Skip to content

Add highlighting (on search / filter) to DataTable #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 31 additions & 33 deletions components/data-table/cell.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,38 @@ const { PropTypes } = React;
/**
* 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.
*/
const DataTableCell = React.createClass({
// ### Display Name
// Always use the canonical component name as the React display name.
displayName: DATA_TABLE_CELL,
const DataTableCell = (props) => (
<td className={props.className} data-label={props.label}>
{props.children}
</td>
);

// ### Display Name
// Always use the canonical component name as the React display name.
DataTableCell.displayName = DATA_TABLE_CELL;

// ### Prop Types
propTypes: {
/**
* Class names to be added to the cell.
*/
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
/**
* The item from the items which represents this row.
*/
item: PropTypes.object,
/**
* The column label.
*/
label: PropTypes.string,
/**
* The property of this item to display.
*/
property: PropTypes.string
},

// ### Render
// Should return a `<td></td>`.
render () {
return (
<td className={this.props.className} data-label={this.props.label}>
{this.props.item[this.props.property]}
</td>
);
}
});
DataTableCell.propTypes = {
/**
* The contents of the cell. Equivalent to `props.item[props.property]`
*/
children: PropTypes.node,
/**
* Class names to be added to the cell.
*/
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
/**
* The item from the items which represents this row.
*/
item: PropTypes.object,
/**
* The column label.
*/
label: PropTypes.string,
/**
* The property of this item to display.
*/
property: PropTypes.string
};

module.exports = DataTableCell;
54 changes: 54 additions & 0 deletions components/data-table/highlight-cell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright (c) 2015, salesforce.com, inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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.
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.

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.
*/

// ### React
import React from 'react';

// ## Children
import DataTableCell from './cell';
import Highlighter from '../utilities/highlighter';

// ## Constants
import { DATA_TABLE_CELL } from '../../utilities/constants';

// Removes the need for `PropTypes`.
const { PropTypes } = React;

/**
* A Cell renderer for the DataTable that automatically highlights search text.
*/
const DataTableHighlightCell = (props) => (
<DataTableCell {...props}>
<Highlighter search={props.search}>{props.children}</Highlighter>
</DataTableCell>
);

// ### Display Name
// The DataTable looks for components with this name to determine what it should use to render a given column's cells.
DataTableHighlightCell.displayName = DATA_TABLE_CELL;

// ### Prop Types
DataTableHighlightCell.propTypes = {
/**
* The contents of the cell. Equivalent to `props.item[props.property]`
*/
children: PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.bool
]),
/**
* The string of text (or Regular Expression) to highlight.
*/
search: PropTypes.any
};

module.exports = DataTableHighlightCell;
13 changes: 11 additions & 2 deletions components/data-table/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import React from 'react';
// ### classNames
import classNames from 'classnames';

// ### assign
import assign from 'lodash.assign';

// ### isArray
import isArray from 'lodash.isarray';

Expand Down Expand Up @@ -162,19 +165,25 @@ const DataTable = React.createClass({
if (child && child.type === DataTableColumn) {
const {
children,
...props
...columnProps
} = child.props;

const props = assign({}, this.props);
delete props.children;
assign(props, columnProps);

let Cell;
if (children && children.type.displayName === DATA_TABLE_CELL) {
Cell = children.type;
assign(props, children.props);
} else {
Cell = DataTableCell;
}

columns.push({
Cell,
props
props,
dataTableProps: this.props
});
} else if (child && child.type === DataTableRowActions) {
RowActions = child;
Expand Down
4 changes: 3 additions & 1 deletion components/data-table/row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ const DataTableRow = React.createClass({
id={`${this.props.id}-${DATA_TABLE_CELL}-${index}`}
item={this.props.item}
key={column.props.property}
/>
>
{this.props.item[column.props.property]}
</Cell>
);
})}
{this.props.rowActions ?
Expand Down
58 changes: 58 additions & 0 deletions components/utilities/highlighter/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright (c) 2015, salesforce.com, inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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.
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.

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.
*/

// ### React
import React from 'react';

// ### ReactHighlighter
import ReactHighlighter from 'react-highlighter';

// ## Constants
import { HIGHLIGHTER } from '../../../utilities/constants';

// Removes the need for `PropTypes`.
const { PropTypes } = React;

/**
* A utility component that highlights occurrences of a particular pattern in its contents.
*/
const Highlighter = (props) => {
if (props.search) {
return (
<ReactHighlighter matchClass={null} matchElement="mark" search={props.search}>
{props.children}
</ReactHighlighter>
);
}

return <span>{props.children}</span>;
};

// ### Display Name
Highlighter.displayName = HIGHLIGHTER;

// ### Prop Types
Highlighter.propTypes = {
/**
* The full string to display.
*/
children: PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.bool
]),
/**
* The string of text (or Regular Expression) to highlight.
*/
search: PropTypes.any
};

module.exports = Highlighter;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"lodash.partial": "^4.1.3",
"lodash.reject": "^4.3.0",
"lodash.uniqueid": "^4.0.0",
"react-highlighter": "^0.3.2",
"react-modal": "^0.6.1",
"react-onclickoutside": "^4.5.0",
"tether": "1.1.0",
Expand Down
116 changes: 116 additions & 0 deletions stories/card/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import uniqueId from 'lodash.uniqueid';
import { storiesOf, action } from '@kadira/storybook';

import { CARD } from '../../utilities/constants';
import Button from '../../components/button';
import Card from '../../components/card';
import CardEmpty from '../../components/card/empty';
import CardFilter from '../../components/card/filter';
import DataTable from '../../components/data-table';
import DataTableColumn from '../../components/data-table/column';
import DataTableHighlightCell from '../../components/data-table/highlight-cell';
import Icon from '../../components/icon';

const sampleItems = [
{ name: 'Cloudhub' },
{ name: 'Cloudhub + Anypoint Connectors' },
{ name: 'Cloud City' }
];

const DemoCard = React.createClass({
displayName: 'DemoCard',

propTypes: {
items: React.PropTypes.array
},

getInitialState () {
return {
filter: null,
items: this.props.items
};
},

render () {
let items = this.state.items;
if (this.state.filter) {
items = items.filter((item) => this.state.filter.test(item.name));
}

const isEmpty = (items.length === 0);

return (
<div className="slds-grid slds-grid--vertical">
<Card
id="ExampleCard"
filter={(!isEmpty || this.state.filter)
? <CardFilter onChange={this.handleFilterChange} />
: null
}
headerActions={!isEmpty
? <Button label="Delete All Items" onClick={this.handleDeleteAllItems} />
: null
}
heading="Releated Items"
icon={<Icon category="standard" name="default" size="small" />}
empty={isEmpty
? <CardEmpty heading="No Related Items">
<Button label="Add Item" onClick={this.handleAddItem} />
</CardEmpty>
: null}
>
<DataTable
id="SLDSDataTableExample-1"
items={items}
bordered
>
<DataTableColumn
label="Opportunity Name"
property="name"
truncate
>
<DataTableHighlightCell search={this.state.filter} />
</DataTableColumn>
</DataTable>
</Card>

</div>
);
},

handleFilterChange (event, ...rest) {
action('filter')(event, ...rest);

const filter = event.target.value !== '' ? RegExp(event.target.value, 'i') : null;

this.setState({
filter
});
},

handleDeleteAllItems (...rest) {
action('delete all')(...rest);

this.setState({
filter: null,
items: []
});
},

handleAddItem (...rest) {
action('add')(...rest);

this.setState({
items: [
{ name: uniqueId('New item #') },
...this.state.items
]
});
}
});

storiesOf(CARD, module)
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
.add('w/ items', () => <DemoCard items={sampleItems} />)
.add('empty', () => <DemoCard items={[]} />);
16 changes: 7 additions & 9 deletions stories/data-table/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable indent */

import React from 'react';
import { storiesOf, action } from '@kadira/storybook';

Expand Down Expand Up @@ -63,14 +61,14 @@ const DemoDataTable = React.createClass({
);
},

handleChange (selection) {
action('change')(...arguments);
handleChange (selection, ...rest) {
action('change')(selection, ...rest);

this.setState({ selection });
},

handleSort (sortColumn) {
action('sort')(...arguments);
handleSort (sortColumn, ...rest) {
action('sort')(sortColumn, ...rest);

const sortProperty = sortColumn.property;
const sortDirection = sortColumn.sortDirection;
Expand Down Expand Up @@ -99,6 +97,6 @@ const DemoDataTable = React.createClass({

storiesOf(DATA_TABLE, module)
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
.add('striped', () => <DemoDataTable striped={true} />)
.add('bordered', () => <DemoDataTable bordered={true} />)
.add('selectable', () => <DemoDataTable selectRows={true} />);
.add('striped', () => <DemoDataTable striped />)
.add('bordered', () => <DemoDataTable bordered />)
.add('selectable', () => <DemoDataTable selectRows />);
Loading