`Search for '${txt}'`}
+ onChange={(selected) => onChange(selected)}
+ newOptionCreator={({ label, labelKey, valueKey }) => {
+ const option = {};
+ option[valueKey] = label.toLowerCase();
+ option[labelKey] = label.toLowerCase();
+ option.textFilter = true;
+ option.className = 'Select-create-option-placeholder';
+ return option;
+ }}
value={value}
- placeholder="Filter data with any text"
+ multi
style={{
margin: '1rem 0 1rem 0',
}}
diff --git a/src/reducer.js b/src/reducer.js
index 1f8acb7..a3ee9f5 100644
--- a/src/reducer.js
+++ b/src/reducer.js
@@ -15,7 +15,7 @@ import {
const defaultState = (configs = {}) => ({
page: 0,
pageSize: configs.defaultPageSize || 5,
- filter: '',
+ filter: [],
sortKey: null,
direction: null,
selectAll: false,
@@ -72,7 +72,7 @@ const behaviours = {
[TABLE_FILTER_CHANGED]: (state, { payload }) => ({
...state,
page: 0,
- filter: payload.filter.toLowerCase(),
+ filter: payload.filter,
}),
[TABLE_SELECT_ALL_CHANGED]: (state) => ({
...state,
diff --git a/src/selectors.js b/src/selectors.js
index 20dcd98..f764dcd 100644
--- a/src/selectors.js
+++ b/src/selectors.js
@@ -9,17 +9,30 @@ function paginate(rows, { page, pageSize }) {
return rows.slice(start, start + pageSize);
}
-function filter(rows = [], textFilter, columns) {
- if (!textFilter) {
+function filter(rows = [], filters = [], columns) {
+ if (filters.length === 0) {
return rows.slice(0);
}
- return _.filter(rows, (row) => _.some(columns, (column) => {
+
+ // apply text filter across all columns
+ let filteredRows = _.filter(rows, row => _.some(columns, (column) => {
if (!column.filterable) {
return false;
}
const normalized = String(_.get(row, column.key)).toLowerCase();
- return normalized.indexOf(textFilter) > -1;
+ return _.every(filters, f => !f.textFilter || normalized.indexOf(f.value) > -1);
+ }));
+
+ // apply value filters on taggable columns
+ filteredRows = _.filter(filteredRows, row => _.every(columns, column => {
+ if (!column.taggable) {
+ return true;
+ }
+ const value = _.get(row, column.key);
+ return _.every(filters, f => !f.valueFilter || f.key !== column.key || f.value === value);
}));
+
+ return filteredRows;
}
function sort(rows, { sortKey, direction }) {
@@ -71,6 +84,49 @@ export default (tableName) => {
(initialData, textFilter, columns) => filter(initialData, textFilter, columns)
);
+ const getFilterOptions = createSelector(
+ getInitialData,
+ getColumns,
+ (initialData, columns) => {
+ const options = [];
+ const values = {};
+ const columnMap = _.keyBy(columns, 'key');
+
+ initialData.forEach(row => {
+ columns.forEach(column => {
+ if (!column.taggable) {
+ return;
+ }
+ if (!values[column.key]) {
+ values[column.key] = [];
+ }
+ const columnValues = values[column.key];
+ const value = _.get(row, column.key);
+ if (!columnValues.includes(value)) {
+ columnValues.push(value);
+ }
+ });
+ });
+
+ _.forOwn(values, (columnValues, key) => {
+ columnValues.forEach(value => {
+ let labelValue = value;
+ if (_.isBoolean(value)) {
+ labelValue = value ? 'Yes' : 'No';
+ }
+ options.push({
+ label: `${columnMap[key].header}:${labelValue}`,
+ valueFilter: true,
+ value,
+ key,
+ });
+ });
+ });
+
+ return options;
+ }
+ );
+
const getPageInfo = createSelector(
getPage,
getPageSize,
@@ -151,5 +207,6 @@ export default (tableName) => {
getSelectedRows,
getSelectAll,
getPrimaryKey,
+ getFilterOptions,
};
};
diff --git a/src/sematable.js b/src/sematable.js
index 19d11ac..f109746 100644
--- a/src/sematable.js
+++ b/src/sematable.js
@@ -21,12 +21,13 @@ const propTypes = {
isInitialized: PropTypes.bool.isRequired,
visibleRows: PropTypes.array,
- filter: PropTypes.string,
+ filter: PropTypes.array,
sortInfo: PropTypes.object,
pageInfo: PropTypes.object,
selectAll: PropTypes.bool,
selectedRows: PropTypes.array,
primaryKey: PropTypes.string,
+ filterOptions: PropTypes.array,
onPageChange: PropTypes.func.isRequired,
onPageSizeChange: PropTypes.func.isRequired,
@@ -71,6 +72,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => {
selectAll: selectors.getSelectAll(state),
selectedRows: selectors.getSelectedRows(state),
primaryKey: selectors.getPrimaryKey(state),
+ filterOptions: selectors.getFilterOptions(state),
};
};
@@ -109,6 +111,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => {
selectAll,
selectedRows,
primaryKey,
+ filterOptions,
/* actions */
onPageChange,
@@ -164,6 +167,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => {
{showFilter && onFilterChange(f)}
/>}
diff --git a/stories/UsersTable.js b/stories/UsersTable.js
index ebfe88c..2dccdb9 100644
--- a/stories/UsersTable.js
+++ b/stories/UsersTable.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import sematable, { Table } from '../src';
+import YesNo from './YesNo.js';
export const USERS_TABLE = 'usersTable';
const columns = [
@@ -7,6 +8,7 @@ const columns = [
{ key: 'firstName', header: 'First name', filterable: true, sortable: true },
{ key: 'lastName', header: 'Last name', filterable: true, sortable: true },
{ key: 'status', header: 'Status', taggable: true },
+ { key: 'confirmed', header: 'Confirmed', taggable: true, Component: YesNo },
];
class UsersTable extends Component {
diff --git a/stories/YesNo.js b/stories/YesNo.js
new file mode 100644
index 0000000..0935f3c
--- /dev/null
+++ b/stories/YesNo.js
@@ -0,0 +1,10 @@
+import React, { PropTypes } from 'react';
+
+const YesNo = ({ children }) => (
+ {children ? 'Yes' : 'No'}
+);
+
+YesNo.propTypes = {
+ children: PropTypes.bool.isRequired,
+};
+export default YesNo;
diff --git a/stories/index.js b/stories/index.js
index 263a9ec..89d70b4 100644
--- a/stories/index.js
+++ b/stories/index.js
@@ -14,12 +14,14 @@ const users = [
firstName: 'John',
lastName: 'Doe',
status: 'UNKNOWN',
+ confirmed: true,
},
{
id: 2,
firstName: 'Bob',
lastName: 'McBobber',
status: 'ACTIVE',
+ confirmed: false,
},
];