Skip to content

Commit eea01f7

Browse files
authored
Merge pull request #328 from tweettypography/highlight-cell
Add highlighting (on search / filter) to DataTable
2 parents d9cef40 + 4db225e commit eea01f7

File tree

11 files changed

+315
-54
lines changed

11 files changed

+315
-54
lines changed

components/data-table/cell.jsx

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,38 @@ const { PropTypes } = React;
2121
/**
2222
* The default Cell renderer for the DataTable. Pass in any React component with the same `displayName` which takes the same props to provide custom rendering.
2323
*/
24-
const DataTableCell = React.createClass({
25-
// ### Display Name
26-
// Always use the canonical component name as the React display name.
27-
displayName: DATA_TABLE_CELL,
24+
const DataTableCell = (props) => (
25+
<td className={props.className} data-label={props.label}>
26+
{props.children}
27+
</td>
28+
);
29+
30+
// ### Display Name
31+
// Always use the canonical component name as the React display name.
32+
DataTableCell.displayName = DATA_TABLE_CELL;
2833

2934
// ### Prop Types
30-
propTypes: {
31-
/**
32-
* Class names to be added to the cell.
33-
*/
34-
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
35-
/**
36-
* The item from the items which represents this row.
37-
*/
38-
item: PropTypes.object,
39-
/**
40-
* The column label.
41-
*/
42-
label: PropTypes.string,
43-
/**
44-
* The property of this item to display.
45-
*/
46-
property: PropTypes.string
47-
},
48-
49-
// ### Render
50-
// Should return a `<td></td>`.
51-
render () {
52-
return (
53-
<td className={this.props.className} data-label={this.props.label}>
54-
{this.props.item[this.props.property]}
55-
</td>
56-
);
57-
}
58-
});
35+
DataTableCell.propTypes = {
36+
/**
37+
* The contents of the cell. Equivalent to `props.item[props.property]`
38+
*/
39+
children: PropTypes.node,
40+
/**
41+
* Class names to be added to the cell.
42+
*/
43+
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
44+
/**
45+
* The item from the items which represents this row.
46+
*/
47+
item: PropTypes.object,
48+
/**
49+
* The column label.
50+
*/
51+
label: PropTypes.string,
52+
/**
53+
* The property of this item to display.
54+
*/
55+
property: PropTypes.string
56+
};
5957

6058
module.exports = DataTableCell;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright (c) 2015, salesforce.com, inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7+
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8+
9+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10+
*/
11+
12+
// ### React
13+
import React from 'react';
14+
15+
// ## Children
16+
import DataTableCell from './cell';
17+
import Highlighter from '../utilities/highlighter';
18+
19+
// ## Constants
20+
import { DATA_TABLE_CELL } from '../../utilities/constants';
21+
22+
// Removes the need for `PropTypes`.
23+
const { PropTypes } = React;
24+
25+
/**
26+
* A Cell renderer for the DataTable that automatically highlights search text.
27+
*/
28+
const DataTableHighlightCell = (props) => (
29+
<DataTableCell {...props}>
30+
<Highlighter search={props.search}>{props.children}</Highlighter>
31+
</DataTableCell>
32+
);
33+
34+
// ### Display Name
35+
// The DataTable looks for components with this name to determine what it should use to render a given column's cells.
36+
DataTableHighlightCell.displayName = DATA_TABLE_CELL;
37+
38+
// ### Prop Types
39+
DataTableHighlightCell.propTypes = {
40+
/**
41+
* The contents of the cell. Equivalent to `props.item[props.property]`
42+
*/
43+
children: PropTypes.oneOfType([
44+
React.PropTypes.string,
45+
React.PropTypes.number,
46+
React.PropTypes.bool
47+
]),
48+
/**
49+
* The string of text (or Regular Expression) to highlight.
50+
*/
51+
search: PropTypes.any
52+
};
53+
54+
module.exports = DataTableHighlightCell;

components/data-table/index.jsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import React from 'react';
2121
// ### classNames
2222
import classNames from 'classnames';
2323

24+
// ### assign
25+
import assign from 'lodash.assign';
26+
2427
// ### isArray
2528
import isArray from 'lodash.isarray';
2629

@@ -162,19 +165,25 @@ const DataTable = React.createClass({
162165
if (child && child.type === DataTableColumn) {
163166
const {
164167
children,
165-
...props
168+
...columnProps
166169
} = child.props;
167170

171+
const props = assign({}, this.props);
172+
delete props.children;
173+
assign(props, columnProps);
174+
168175
let Cell;
169176
if (children && children.type.displayName === DATA_TABLE_CELL) {
170177
Cell = children.type;
178+
assign(props, children.props);
171179
} else {
172180
Cell = DataTableCell;
173181
}
174182

175183
columns.push({
176184
Cell,
177-
props
185+
props,
186+
dataTableProps: this.props
178187
});
179188
} else if (child && child.type === DataTableRowActions) {
180189
RowActions = child;

components/data-table/row.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ const DataTableRow = React.createClass({
8181
id={`${this.props.id}-${DATA_TABLE_CELL}-${index}`}
8282
item={this.props.item}
8383
key={column.props.property}
84-
/>
84+
>
85+
{this.props.item[column.props.property]}
86+
</Cell>
8587
);
8688
})}
8789
{this.props.rowActions ?
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright (c) 2015, salesforce.com, inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7+
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8+
9+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10+
*/
11+
12+
// ### React
13+
import React from 'react';
14+
15+
// ### ReactHighlighter
16+
import ReactHighlighter from 'react-highlighter';
17+
18+
// ## Constants
19+
import { HIGHLIGHTER } from '../../../utilities/constants';
20+
21+
// Removes the need for `PropTypes`.
22+
const { PropTypes } = React;
23+
24+
/**
25+
* A utility component that highlights occurrences of a particular pattern in its contents.
26+
*/
27+
const Highlighter = (props) => {
28+
if (props.search) {
29+
return (
30+
<ReactHighlighter matchClass={null} matchElement="mark" search={props.search}>
31+
{props.children}
32+
</ReactHighlighter>
33+
);
34+
}
35+
36+
return <span>{props.children}</span>;
37+
};
38+
39+
// ### Display Name
40+
Highlighter.displayName = HIGHLIGHTER;
41+
42+
// ### Prop Types
43+
Highlighter.propTypes = {
44+
/**
45+
* The full string to display.
46+
*/
47+
children: PropTypes.oneOfType([
48+
React.PropTypes.string,
49+
React.PropTypes.number,
50+
React.PropTypes.bool
51+
]),
52+
/**
53+
* The string of text (or Regular Expression) to highlight.
54+
*/
55+
search: PropTypes.any
56+
};
57+
58+
module.exports = Highlighter;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"lodash.partial": "^4.1.3",
5656
"lodash.reject": "^4.3.0",
5757
"lodash.uniqueid": "^4.0.0",
58+
"react-highlighter": "^0.3.2",
5859
"react-modal": "^0.6.1",
5960
"react-onclickoutside": "^4.5.0",
6061
"tether": "1.1.0",

stories/card/index.jsx

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React from 'react';
2+
import uniqueId from 'lodash.uniqueid';
3+
import { storiesOf, action } from '@kadira/storybook';
4+
5+
import { CARD } from '../../utilities/constants';
6+
import Button from '../../components/button';
7+
import Card from '../../components/card';
8+
import CardEmpty from '../../components/card/empty';
9+
import CardFilter from '../../components/card/filter';
10+
import DataTable from '../../components/data-table';
11+
import DataTableColumn from '../../components/data-table/column';
12+
import DataTableHighlightCell from '../../components/data-table/highlight-cell';
13+
import Icon from '../../components/icon';
14+
15+
const sampleItems = [
16+
{ name: 'Cloudhub' },
17+
{ name: 'Cloudhub + Anypoint Connectors' },
18+
{ name: 'Cloud City' }
19+
];
20+
21+
const DemoCard = React.createClass({
22+
displayName: 'DemoCard',
23+
24+
propTypes: {
25+
items: React.PropTypes.array
26+
},
27+
28+
getInitialState () {
29+
return {
30+
filter: null,
31+
items: this.props.items
32+
};
33+
},
34+
35+
render () {
36+
let items = this.state.items;
37+
if (this.state.filter) {
38+
items = items.filter((item) => this.state.filter.test(item.name));
39+
}
40+
41+
const isEmpty = (items.length === 0);
42+
43+
return (
44+
<div className="slds-grid slds-grid--vertical">
45+
<Card
46+
id="ExampleCard"
47+
filter={(!isEmpty || this.state.filter)
48+
? <CardFilter onChange={this.handleFilterChange} />
49+
: null
50+
}
51+
headerActions={!isEmpty
52+
? <Button label="Delete All Items" onClick={this.handleDeleteAllItems} />
53+
: null
54+
}
55+
heading="Releated Items"
56+
icon={<Icon category="standard" name="default" size="small" />}
57+
empty={isEmpty
58+
? <CardEmpty heading="No Related Items">
59+
<Button label="Add Item" onClick={this.handleAddItem} />
60+
</CardEmpty>
61+
: null}
62+
>
63+
<DataTable
64+
id="SLDSDataTableExample-1"
65+
items={items}
66+
bordered
67+
>
68+
<DataTableColumn
69+
label="Opportunity Name"
70+
property="name"
71+
truncate
72+
>
73+
<DataTableHighlightCell search={this.state.filter} />
74+
</DataTableColumn>
75+
</DataTable>
76+
</Card>
77+
78+
</div>
79+
);
80+
},
81+
82+
handleFilterChange (event, ...rest) {
83+
action('filter')(event, ...rest);
84+
85+
const filter = event.target.value !== '' ? RegExp(event.target.value, 'i') : null;
86+
87+
this.setState({
88+
filter
89+
});
90+
},
91+
92+
handleDeleteAllItems (...rest) {
93+
action('delete all')(...rest);
94+
95+
this.setState({
96+
filter: null,
97+
items: []
98+
});
99+
},
100+
101+
handleAddItem (...rest) {
102+
action('add')(...rest);
103+
104+
this.setState({
105+
items: [
106+
{ name: uniqueId('New item #') },
107+
...this.state.items
108+
]
109+
});
110+
}
111+
});
112+
113+
storiesOf(CARD, module)
114+
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
115+
.add('w/ items', () => <DemoCard items={sampleItems} />)
116+
.add('empty', () => <DemoCard items={[]} />);

stories/data-table/index.jsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable indent */
2-
31
import React from 'react';
42
import { storiesOf, action } from '@kadira/storybook';
53

@@ -63,14 +61,14 @@ const DemoDataTable = React.createClass({
6361
);
6462
},
6563

66-
handleChange (selection) {
67-
action('change')(...arguments);
64+
handleChange (selection, ...rest) {
65+
action('change')(selection, ...rest);
6866

6967
this.setState({ selection });
7068
},
7169

72-
handleSort (sortColumn) {
73-
action('sort')(...arguments);
70+
handleSort (sortColumn, ...rest) {
71+
action('sort')(sortColumn, ...rest);
7472

7573
const sortProperty = sortColumn.property;
7674
const sortDirection = sortColumn.sortDirection;
@@ -99,6 +97,6 @@ const DemoDataTable = React.createClass({
9997

10098
storiesOf(DATA_TABLE, module)
10199
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
102-
.add('striped', () => <DemoDataTable striped={true} />)
103-
.add('bordered', () => <DemoDataTable bordered={true} />)
104-
.add('selectable', () => <DemoDataTable selectRows={true} />);
100+
.add('striped', () => <DemoDataTable striped />)
101+
.add('bordered', () => <DemoDataTable bordered />)
102+
.add('selectable', () => <DemoDataTable selectRows />);

0 commit comments

Comments
 (0)