diff --git a/docs/columns.md b/docs/columns.md
index 38594d5..6546f81 100644
--- a/docs/columns.md
+++ b/docs/columns.md
@@ -13,11 +13,13 @@ Available properties in a column object:
* [title](#title)
* [events](#events)
* [align](#align)
+* [attrs](#attrs)
* [headerTitle](#headerTitle)
* [headerEvents](#headerEvents)
* [headerAlign](#headerAlign)
* [headerClasses](#headerClasses)
* [headerStyle](#headerStyle)
+* [headerAttrs](#headerAttrs)
Following is a most simplest and basic usage:
@@ -189,4 +191,40 @@ You can assign any [HTML Event](https://www.w3schools.com/tags/ref_eventattribut
```
## column.headerEvents - [Object]
-`headerEvents` same as [`column.events`](#events) but this is for header column.
\ No newline at end of file
+`headerEvents` same as [`column.events`](#events) but this is for header column.
+
+## column.attrs - [Object | Function]
+Via `attrs` property, You can costomize table column [HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes) which allow user to configure the elements or adjust their behavior. It takes `Object` and `callback function` is also acceptable.
+
+```js
+{
+ // omit...
+ attrs: (cell, row, colIndex) => ({
+ // return customized HTML attribute here
+ })
+}
+```
+
+#### * Caution
+
+If `column.classes`, `column.style`, `column.title`, `column.hidden` or `column.align` was given at the same time, property `attrs` has lower priorty and it will be overwrited.
+
+```js
+{
+ // omit...
+ title: true, // it will be chosen.
+ attrs: { title: 'test' }
+}
+```
+
+## column.headerAttrs - [Object | Function]
+`headerAttrs` is similiar to [`column.attrs`](#attrs) but it's for header column.
+
+```js
+{
+ // omit...
+ headerAttrs: (column, colIndex) => ({
+ // return customized HTML attribute here
+ })
+}
+```
diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js
new file mode 100644
index 0000000..bb25ad5
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/columns/column-attrs-table.js
@@ -0,0 +1,45 @@
+/* eslint no-unused-vars: 0 */
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const products = productsGenerator();
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID',
+ attrs: { title: 'id column' }
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ attrs: (cell, row, colIndex) => ({ 'data-test': `customized data ${colIndex}` })
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const sourceCode = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID',
+ attrs: { title: 'id column' }
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ attrs: (cell, row, colIndex) => ({ 'data-test': \`customized data \${colIndex}\` })
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js b/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js
new file mode 100644
index 0000000..ad2c5d9
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/header-columns/column-attrs-table.js
@@ -0,0 +1,45 @@
+/* eslint no-unused-vars: 0 */
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const products = productsGenerator();
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID',
+ headerAttrs: { title: 'ID header column' }
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ headerAttrs: (column, colIndex) => ({ 'data-test': `customized data ${colIndex}` })
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const sourceCode = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID',
+ headerAttrs: { title: 'ID header column' }
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ headerAttrs: (column, colIndex) => ({ 'data-test': \`customized data \${colIndex}\` })
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index d63f949..16c3bae 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -20,6 +20,7 @@ import ColumnAlignTable from 'examples/columns/column-align-table';
import ColumnTitleTable from 'examples/columns/column-title-table';
import ColumnEventTable from 'examples/columns/column-event-table';
import ColumnHiddenTable from 'examples/columns/column-hidden-table';
+import ColumnAttrsTable from 'examples/columns/column-attrs-table';
// work on header columns
import HeaderColumnFormatTable from 'examples/header-columns/column-format-table';
@@ -28,6 +29,7 @@ import HeaderColumnTitleTable from 'examples/header-columns/column-title-table';
import HeaderColumnEventTable from 'examples/header-columns/column-event-table';
import HeaderColumnClassTable from 'examples/header-columns/column-class-table';
import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
+import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
// css style
import 'bootstrap/dist/css/bootstrap.min.css';
@@ -56,7 +58,8 @@ storiesOf('Work on Columns', module)
.add('Column Hidden', () => )
.add('Column Event', () => )
.add('Customize Column Class', () => )
- .add('Customize Column Style', () => );
+ .add('Customize Column Style', () => )
+ .add('Customize Column HTML attribute', () => );
storiesOf('Work on Header Columns', module)
.add('Column Formatter', () => )
@@ -64,4 +67,5 @@ storiesOf('Work on Header Columns', module)
.add('Column Title', () => )
.add('Column Event', () => )
.add('Customize Column Class', () => )
- .add('Customize Column Style', () => );
+ .add('Customize Column Style', () => )
+ .add('Customize Column HTML attribute', () => );
diff --git a/packages/react-bootstrap-table2/src/cell.js b/packages/react-bootstrap-table2/src/cell.js
index 8f590f5..4cb1621 100644
--- a/packages/react-bootstrap-table2/src/cell.js
+++ b/packages/react-bootstrap-table2/src/cell.js
@@ -13,15 +13,18 @@ const Cell = ({ row, rowIndex, column, columnIndex }) => {
classes,
title,
events,
- align
+ align,
+ attrs
} = column;
let cellTitle;
let cellStyle = {};
let content = _.get(row, dataField);
- const attrs = {
+ const cellAttrs = {
+ ..._.isFunction(attrs) ? attrs(content, row, columnIndex) : attrs,
...events
};
+
const cellClasses = _.isFunction(classes) ? classes(content, row, columnIndex) : classes;
if (style) {
@@ -30,7 +33,7 @@ const Cell = ({ row, rowIndex, column, columnIndex }) => {
if (title) {
cellTitle = _.isFunction(title) ? title(content, row, columnIndex) : content;
- attrs.title = cellTitle;
+ cellAttrs.title = cellTitle;
}
if (formatter) {
@@ -41,15 +44,16 @@ const Cell = ({ row, rowIndex, column, columnIndex }) => {
cellStyle.textAlign = _.isFunction(align) ? align(content, row, columnIndex) : align;
}
- attrs.style = cellStyle;
- attrs.className = cellClasses;
-
if (hidden) {
- attrs.style.display = 'none';
+ cellStyle.display = 'none';
}
+ if (cellClasses) cellAttrs.className = cellClasses;
+
+ if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
+
return (
- { content } |
+ { content } |
);
};
diff --git a/packages/react-bootstrap-table2/src/header-cell.js b/packages/react-bootstrap-table2/src/header-cell.js
index 062e4f6..be2e525 100644
--- a/packages/react-bootstrap-table2/src/header-cell.js
+++ b/packages/react-bootstrap-table2/src/header-cell.js
@@ -13,9 +13,12 @@ const HeaderCell = ({ column, index }) => {
headerFormatter,
headerEvents,
headerClasses,
- headerStyle
+ headerStyle,
+ headerAttrs
} = column;
- const attrs = {
+
+ const cellAttrs = {
+ ..._.isFunction(headerAttrs) ? headerAttrs(column, index) : headerAttrs,
...headerEvents
};
const children = headerFormatter ? headerFormatter(column, index) : text;
@@ -28,22 +31,23 @@ const HeaderCell = ({ column, index }) => {
}
if (headerTitle) {
- attrs.title = _.isFunction(headerTitle) ? headerTitle(column, index) : text;
+ cellAttrs.title = _.isFunction(headerTitle) ? headerTitle(column, index) : text;
}
if (headerAlign) {
cellStyle.textAlign = _.isFunction(headerAlign) ? headerAlign(column, index) : headerAlign;
}
- attrs.style = cellStyle;
- attrs.className = cellClasses;
-
if (hidden) {
- attrs.style.display = 'none';
+ cellStyle.display = 'none';
}
+ if (cellClasses) cellAttrs.className = cellClasses;
+
+ if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
+
return (
-
+ |
{ children }
|
);
diff --git a/packages/react-bootstrap-table2/src/utils.js b/packages/react-bootstrap-table2/src/utils.js
index bee745a..e86be0f 100644
--- a/packages/react-bootstrap-table2/src/utils.js
+++ b/packages/react-bootstrap-table2/src/utils.js
@@ -17,7 +17,32 @@ function isFunction(obj) {
return obj && (typeof obj === 'function');
}
+/**
+ * Checks if `value` is the Object. the `Object` except `Function` and `Array.`
+ *
+ * @param {*} obj - The value gonna check
+ */
+function isObject(obj) {
+ const type = typeof obj;
+ return obj !== null && type === 'object' && obj.constructor === Object;
+}
+
+function isEmptyObject(obj) {
+ if (!isObject(obj)) return false;
+
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
+ const keys = Object.keys(obj);
+
+ for (let i = 0; i < keys.length; i += 1) {
+ if (hasOwnProperty.call(obj, keys[i])) return false;
+ }
+
+ return true;
+}
+
export default {
get,
- isFunction
+ isFunction,
+ isObject,
+ isEmptyObject
};
diff --git a/packages/react-bootstrap-table2/test/cell.test.js b/packages/react-bootstrap-table2/test/cell.test.js
index aebc348..1d1bebb 100644
--- a/packages/react-bootstrap-table2/test/cell.test.js
+++ b/packages/react-bootstrap-table2/test/cell.test.js
@@ -305,4 +305,130 @@ describe('Cell', () => {
});
});
});
+
+ describe('when column.attrs prop is defined', () => {
+ let column;
+ const columnIndex = 1;
+
+ beforeEach(() => {
+ column = {
+ dataField: 'id',
+ text: 'ID'
+ };
+ });
+
+ describe('when attrs is an object', () => {
+ it('should render column.attrs correctly', () => {
+ column.attrs = {
+ 'data-test': 'test',
+ title: 'title',
+ className: 'attrs-class',
+ style: {
+ backgroundColor: 'attrs-style-test',
+ display: 'none',
+ textAlign: 'right'
+ }
+ };
+ wrapper = shallow(
+ | );
+
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('td').prop('data-test')).toEqual(column.attrs['data-test']);
+ expect(wrapper.find('td').prop('title')).toEqual(column.attrs.title);
+ expect(wrapper.hasClass(column.attrs.className)).toBe(true);
+ expect(wrapper.find('td').prop('style')).toEqual(column.attrs.style);
+ expect(wrapper.find('td').prop('style').textAlign).toEqual(column.attrs.style.textAlign);
+ });
+
+ describe('when column.title prop is defined', () => {
+ it('attrs.title should be overwrited', () => {
+ column.title = true;
+ column.attrs = { title: 'title' };
+
+ wrapper = shallow(
+ | );
+
+ expect(wrapper.find('td').prop('title')).toEqual(row[column.dataField]);
+ });
+ });
+
+ describe('when column.classes prop is defined', () => {
+ it('attrs.class should be overwrited', () => {
+ column.classes = 'td-test-class';
+ column.attrs = { className: 'attrs-class' };
+
+ wrapper = shallow(
+ | );
+
+ expect(wrapper.hasClass(column.classes)).toBe(true);
+ });
+ });
+
+ describe('when column.style prop is defined', () => {
+ it('attrs.style should be overwrited', () => {
+ column.style = { backgroundColor: 'red' };
+ column.attrs = { style: { backgroundColor: 'attrs-style-test' } };
+
+ wrapper = shallow(
+ | );
+
+ expect(wrapper.find('td').prop('style')).toEqual(column.style);
+ });
+ });
+
+ describe('when column.hidden prop is defined', () => {
+ it('attrs.style.hidden should be overwrited', () => {
+ column.hidden = true;
+ column.attrs = { style: { hidden: true } };
+
+ wrapper = shallow(
+ | );
+
+ const style = wrapper.find('td').prop('style');
+ expect(style).toBeDefined();
+ expect(style.display).toEqual('none');
+ });
+ });
+
+ describe('when column.align prop is defined', () => {
+ it('attrs.style.textAlign should be overwrited', () => {
+ column.align = 'center';
+ column.attrs = { style: { textAlign: 'right' } };
+
+ wrapper = shallow(
+ | );
+
+ expect(wrapper.find('td').prop('style').textAlign).toEqual(column.align);
+ });
+ });
+ });
+
+ describe('when attrs is custom function', () => {
+ let attrsCallBack;
+ const customAttrs = {
+ title: 'title',
+ 'data-test': 'test'
+ };
+
+ beforeEach(() => {
+ attrsCallBack = sinon.stub()
+ .withArgs(row[column.dataField], row, columnIndex)
+ .returns(customAttrs);
+ column.attrs = attrsCallBack;
+ wrapper = shallow(
+ | );
+ });
+
+ it('should render style.attrs correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('td').prop('data-test')).toEqual(customAttrs['data-test']);
+ expect(wrapper.find('td').prop('title')).toEqual(customAttrs.title);
+ });
+
+ it('should call custom attrs function correctly', () => {
+ expect(attrsCallBack.callCount).toBe(1);
+ expect(attrsCallBack.calledWith(row[column.dataField], row, columnIndex)).toBe(true);
+ });
+ });
+ });
});
diff --git a/packages/react-bootstrap-table2/test/header-cell.test.js b/packages/react-bootstrap-table2/test/header-cell.test.js
index 0dd027a..134de2d 100644
--- a/packages/react-bootstrap-table2/test/header-cell.test.js
+++ b/packages/react-bootstrap-table2/test/header-cell.test.js
@@ -24,9 +24,9 @@ describe('HeaderCell', () => {
expect(wrapper.text()).toEqual(column.text);
});
- it('should have correct default style', () => {
+ it('should not have default style', () => {
const style = wrapper.find('th').prop('style');
- expect(style).toBeDefined();
+ expect(style).toBeUndefined();
});
});
@@ -286,5 +286,103 @@ describe('HeaderCell', () => {
expect(classesCallBack.calledWith(column, index)).toBe(true);
});
});
+
+ describe('when column.headerAttrs prop is defined', () => {
+ beforeEach(() => {
+ column = {
+ dataField: 'id',
+ text: 'ID'
+ };
+ });
+
+ describe('when headerAttrs is an object', () => {
+ it('should render column.headerAttrs correctly', () => {
+ column.headerAttrs = {
+ 'data-test': 'test',
+ title: 'title',
+ className: 'attrs-class',
+ style: { backgroundColor: 'attrs-style-test' }
+ };
+ wrapper = shallow();
+
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('th').prop('data-test')).toEqual(column.headerAttrs['data-test']);
+ expect(wrapper.find('th').prop('title')).toEqual(column.headerAttrs.title);
+ expect(wrapper.hasClass(column.headerAttrs.className)).toBe(true);
+ expect(wrapper.find('th').prop('style')).toEqual(column.headerAttrs.style);
+ });
+
+ describe('when column.headerTitle prop is defined', () => {
+ it('title should be overwrited', () => {
+ column.headerAttrs = { title: 'title' };
+ column.headerTitle = true;
+ wrapper = shallow();
+
+ expect(wrapper.find('th').prop('title')).toBe(column.text);
+ });
+ });
+
+ describe('when column.headerClasses prop is defined', () => {
+ it('class should be overwrited', () => {
+ column.headerClasses = 'td-test-class';
+ column.headerAttrs = { className: 'attrs-class' };
+
+ wrapper = shallow();
+
+ expect(wrapper.hasClass(column.headerClasses)).toBe(true);
+ });
+ });
+
+ describe('when column.headerStyle prop is defined', () => {
+ it('style should be overwrited', () => {
+ column.headerStyle = { backgroundColor: 'red' };
+ column.headerAttrs = { style: { backgroundColor: 'attrs-style-test' } };
+
+ wrapper = shallow();
+
+ expect(wrapper.find('th').prop('style')).toEqual(column.headerStyle);
+ });
+ });
+
+ describe('when column.headerAlign prop is defined', () => {
+ it('style.textAlign should be overwrited', () => {
+ column.headerAlign = 'center';
+ column.headerAttrs = { style: { textAlign: 'right' } };
+
+ wrapper = shallow();
+
+ expect(wrapper.find('th').prop('style').textAlign).toEqual(column.headerAlign);
+ });
+ });
+ });
+
+ describe('when headerAttrs is custom function', () => {
+ let headerAttrsCallBack;
+ const customHeaderAttrs = {
+ title: 'title',
+ 'data-test': 'test'
+ };
+
+
+ beforeEach(() => {
+ headerAttrsCallBack = sinon.stub()
+ .withArgs(column, index)
+ .returns(customHeaderAttrs);
+ column.headerAttrs = headerAttrsCallBack;
+ wrapper = shallow();
+ });
+
+ it('should render style.headerAttrs correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('th').prop('data-test')).toEqual(customHeaderAttrs['data-test']);
+ expect(wrapper.find('th').prop('title')).toEqual(customHeaderAttrs.title);
+ });
+
+ it('should call custom headerAttrs function correctly', () => {
+ expect(headerAttrsCallBack.callCount).toBe(1);
+ expect(headerAttrsCallBack.calledWith(column, index)).toBe(true);
+ });
+ });
+ });
});
});
diff --git a/packages/react-bootstrap-table2/test/utils.test.js b/packages/react-bootstrap-table2/test/utils.test.js
index feb33e9..491cc04 100644
--- a/packages/react-bootstrap-table2/test/utils.test.js
+++ b/packages/react-bootstrap-table2/test/utils.test.js
@@ -21,4 +21,62 @@ describe('Utils', () => {
expect(_.get(data, 'address.not.exist')).toEqual(undefined);
});
});
+
+ describe('isObject', () => {
+ describe('when given Object', () => {
+ it('should return true', () => {
+ expect(_.isObject({})).toBe(true);
+ });
+ });
+
+ describe('when given Function', () => {
+ it('should return false', () => {
+ expect(_.isObject(() => 'test')).toBe(false);
+ });
+ });
+
+ describe('when given Array', () => {
+ it('should return false', () => {
+ expect(_.isObject([])).toBe(false);
+ });
+ });
+
+ describe('when given null', () => {
+ it('should return false', () => {
+ expect(_.isObject(null)).toBe(false);
+ });
+ });
+ });
+
+ describe('isEmptyObject', () => {
+ describe('when given empty Object', () => {
+ it('should return true', () => {
+ expect(_.isEmptyObject({})).toBe(true);
+ });
+ });
+
+ describe('when given non-empty Object', () => {
+ it('should return false', () => {
+ expect(_.isEmptyObject({ foo: 'test' })).toBe(false);
+ });
+ });
+
+ describe('when given Function', () => {
+ it('should return false', () => {
+ expect(_.isEmptyObject(() => 'test')).toBe(false);
+ });
+ });
+
+ describe('when given Array', () => {
+ it('should return false', () => {
+ expect(_.isEmptyObject([])).toBe(false);
+ });
+ });
+
+ describe('when given null', () => {
+ it('should return false', () => {
+ expect(_.isEmptyObject(null)).toBe(false);
+ });
+ });
+ });
});