Skip to content
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

Search by column #89

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
25 changes: 24 additions & 1 deletion demo/src/components/ColumnsControllers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ const ColumnsControllers = ({ controllers }) => {
setColumns(columns);
};

const setSearchText = (column, searchText) => {
column.searchText = searchText;
setColumns(columns);
};

const setSortable = (column) => {
column.sortable = !column.sortable;
setColumns(columns);
Expand Down Expand Up @@ -73,6 +78,19 @@ const ColumnsControllers = ({ controllers }) => {
/>
</ControllerWrappper>
) : null}
{column.searchable &&
column.id !== "checkbox" &&
column.id !== "buttons" ? (
<ControllerWrappper label="Search Text">
<input
type="text"
value={column.searchText}
onChange={(e) =>
setSearchText(column, e.target.value)
}
/>
</ControllerWrappper>
) : null}
<ControllerWrappper label="Visible">
<input
type="checkbox"
Expand All @@ -94,7 +112,12 @@ const ColumnsControllers = ({ controllers }) => {
<input
type="checkbox"
checked={column.searchable}
onChange={() => setSearchable(column)}
onChange={() => {
if (column.searchable) {
setSearchText(column, "");
}
setSearchable(column);
}}
/>
</ControllerWrappper>
) : null}
Expand Down
21 changes: 21 additions & 0 deletions demo/src/components/TableControllers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ const TableControllers = ({ controllers }) => {
}
/>
</ControllerWrappper>
<ControllerWrappper label="Search By Column">
<input
type="checkbox"
checked={controllers.searchByColumn[0]}
onChange={() => {
const shouldSearchByColumn =
!controllers.searchByColumn[0];

if (shouldSearchByColumn) {
const cols = controllers.columns[0].map(
(column) => ({
...column,
searchText: "",
})
);
controllers.columns[1](cols);
}
controllers.searchByColumn[1](shouldSearchByColumn);
}}
/>
</ControllerWrappper>
<ControllerWrappper label="Highlight Search">
<input
type="checkbox"
Expand Down
1 change: 1 addition & 0 deletions demo/src/getColumns.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const getColumns = ({ setRowsData }) => {
pinned: true,
sortable: false,
resizable: false,
searchable: false,
cellRenderer: ButtonsCell,
editorCellRenderer: ButtonsEditorCell,
},
Expand Down
3 changes: 3 additions & 0 deletions demo/src/views/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const MyAwesomeTable = () => {
let [columns, setColumns] = useState(getColumns({ setRowsData }));
let [isSettingsOpen, setIsSettingsOpen] = useState(false);
let [selectAllMode, setSelectAllMode] = useState("page");
let [searchByColumn, setSearchByColumn] = useState(true);

const controllers = {
columns: [columns, setColumns],
Expand All @@ -55,6 +56,7 @@ const MyAwesomeTable = () => {
minSearchChars: [minSearchChars, setMinSearchChars],
minColumnResizeWidth: [minColumnResizeWidth, setMinColumnWidth],
selectAllMode: [selectAllMode, setSelectAllMode],
searchByColumn: [searchByColumn, setSearchByColumn],
};

useEffect(() => {
Expand Down Expand Up @@ -114,6 +116,7 @@ const MyAwesomeTable = () => {
minSearchChars={minSearchChars}
minColumnResizeWidth={minColumnResizeWidth}
selectAllMode={selectAllMode}
searchByColumn={searchByColumn}
/>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/components/CellContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const CellContainer = ({
id,
config: {
highlightSearch,
searchByColumn,
tableHasSelection,
additionalProps: { cellContainer: additionalProps = {} },
},
Expand Down Expand Up @@ -67,6 +68,9 @@ const CellContainer = ({

const getValue = () => {
let value;
const searchToHighlight = searchByColumn
? column.searchText
: searchText;

switch (column.id) {
case "checkbox":
Expand All @@ -89,7 +93,7 @@ const CellContainer = ({
highlightSearch &&
valuePassesSearch(value, column)
)
return getHighlightedText(value, searchText);
return getHighlightedText(value, searchToHighlight);
}

return value;
Expand Down
29 changes: 22 additions & 7 deletions src/components/HeaderCell.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,36 @@ const HeaderCell = ({ column, tableManager }) => {
const {
config: {
additionalProps: { headerCell: additionalProps = {} },
components: { SearchColumn },
searchByColumn,
},
searchApi: { setColumnSearchText },
} = tableManager;

let classNames = (
"rgt-text-truncate " + (additionalProps.className || "")
).trim();

return (
<span
{...additionalProps}
className={classNames}
data-column-id={column.id.toString()}
>
{column.label}
</span>
<div className="rgt-cell-header-content-container">
<span
{...additionalProps}
className={classNames}
data-column-id={column.id.toString()}
>
{column.label}
</span>
{searchByColumn && column.searchable && (
<SearchColumn
tableManager={tableManager}
name={`search-column-${column.field}`}
value={column.searchText}
onChange={(value) =>
setColumnSearchText(column.field, value)
}
/>
)}
</div>
);
};

Expand Down
12 changes: 10 additions & 2 deletions src/components/HeaderCellContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ const HeaderCellContainer = ({ index, column, tableManager }) => {
let {
config: {
isHeaderSticky,
searchByColumn,
components: { DragHandle },
additionalProps: { headerCellContainer: additionalProps = {} },
icons: {
sortAscending: sortAscendingIcon,
sortDescending: sortDescendingIcon,
sortUnsorted: sortUnsortedIcon,
},
},
sortApi: { sort, toggleSort },
Expand Down Expand Up @@ -79,7 +81,9 @@ const HeaderCellContainer = ({ index, column, tableManager }) => {
isPinnedRight
? " rgt-cell-header-pinned rgt-cell-header-pinned-right"
: ""
} ${column.className}`.trim();
}${searchByColumn ? " rgt-cell-header-with-search" : ""} ${
column.className
}`.trim();
}

return (
Expand Down Expand Up @@ -144,7 +148,11 @@ const HeaderCellContainer = ({ index, column, tableManager }) => {
...selectionProps,
})
: column.headerCellRenderer(headerCellProps)}
{sort.colId !== column.id ? null : sort.isAsc ? (
{!column.sortable ? null : sort.colId !== column.id ? (
<span className="rgt-sort-icon rgt-sort-icon-unsorted">
{sortUnsortedIcon}
</span>
) : sort.isAsc ? (
<span className="rgt-sort-icon rgt-sort-icon-ascending">
{sortAscendingIcon}
</span>
Expand Down
23 changes: 23 additions & 0 deletions src/components/SearchColumn.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";

const SearchColumn = ({ tableManager, name, value, onChange }) => {
const {
config: {
texts: { search: searchText },
},
} = tableManager;

return (
<input
name={name}
type="search"
value={value}
onChange={(event) => onChange(event.target.value)}
onClick={(event) => event.stopPropagation()}
placeholder={searchText}
className="rgt-search-column-input"
/>
);
};

export default SearchColumn;
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NoResults from "./NoResults";
import PopoverButton from "./PopoverButton";
import Row from "./Row";
import Search from "./Search";
import SearchColumn from "./SearchColumn";
import Information from "./Information";
import PageSize from "./PageSize";
import Pagination from "./Pagination";
Expand All @@ -35,6 +36,7 @@ export {
PopoverButton,
Row,
Search,
SearchColumn,
Information,
PageSize,
Pagination,
Expand Down
2 changes: 2 additions & 0 deletions src/defaults/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const MENU_ICON = (
const SORT_ASCENDING_ICON = <React.Fragment>&uarr;</React.Fragment>;

const SORT_DESCENDING_ICON = <React.Fragment>&darr;</React.Fragment>;
const SORT_UNSORTED_ICON = <React.Fragment>&#8645;</React.Fragment>;

const SEARCH_ICON = <React.Fragment>&#9906;</React.Fragment>;

Expand All @@ -77,5 +78,6 @@ export default {
columnVisibility: MENU_ICON,
sortAscending: SORT_ASCENDING_ICON,
sortDescending: SORT_DESCENDING_ICON,
sortUnsorted: SORT_UNSORTED_ICON,
search: SEARCH_ICON,
};
1 change: 1 addition & 0 deletions src/hooks/useColumns.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const useColumns = (props, tableManager) => {
editable: true,
sortable: true,
resizable: true,
searchText: "",
search: ({ value, searchText }) =>
value
.toString()
Expand Down
81 changes: 64 additions & 17 deletions src/hooks/useSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useState, useCallback, useRef } from "react";

const useSearch = (props, tableManager) => {
const {
config: { minSearchChars },
columnsApi: { columns },
config: { minSearchChars, searchByColumn },
columnsApi: { columns, setColumns },
} = tableManager;

const searchApi = useRef({}).current;
Expand All @@ -20,45 +20,92 @@ const useSearch = (props, tableManager) => {
props.onSearchTextChange?.(searchText, tableManager);
};

searchApi.setColumnSearchText = (columnField, searchText) => {
const columnsClone = [...columns];
const columnIndex = columnsClone.findIndex(
(col) => col.field === columnField
);

if (columnIndex !== -1) {
columnsClone[columnIndex].searchText = searchText;
setColumns(columnsClone);

props.onColumnSearchTextChange?.(
searchText,
columnField,
tableManager
);
}
};

searchApi.valuePassesSearch = (value, column) => {
if (!value) return false;
if (!column?.searchable) return false;
if (searchApi.searchText.length < minSearchChars) return false;
if (!searchByColumn && searchApi.searchText.length < minSearchChars) {
return false;
} else if (
searchByColumn &&
column.searchText.length < minSearchChars
) {
return false;
}

return column.search({
value: value.toString(),
searchText: searchApi.searchText,
searchText: searchByColumn
? column.searchText
: searchApi.searchText,
});
};

searchApi.searchRows = useCallback(
(rows) => {
var cols = columns.reduce((cols, coldef) => {
cols[coldef.field] = coldef;
return cols;
}, {});
const searchValue = ({ searchText, cellValue, column }) => {
const value = column.getValue({
value: cellValue,
column,
});
return column.search({
value: value?.toString() || "",
searchText,
});
};

if (searchApi.searchText.length >= minSearchChars) {
rows = rows.filter((item) =>
Object.keys(item).some((key) => {
if (cols[key] && cols[key].searchable) {
const value = cols[key].getValue({
value: item[key],
column: cols[key],
});
return cols[key].search({
value: value?.toString() || "",
columns.some((column) => {
if (column.searchable) {
return searchValue({
searchText: searchApi.searchText,
cellValue: item[column.field],
column,
});
}
return false;
})
);
}

if (searchByColumn) {
columns.forEach((column) => {
if (
column.searchable &&
column.searchText.length >= minSearchChars
) {
rows = rows.filter((item) => {
return searchValue({
searchText: column.searchText,
cellValue: item[column.field],
column,
});
});
}
});
}

return rows;
},
[searchApi.searchText, columns, minSearchChars]
[searchApi.searchText, searchByColumn, columns, minSearchChars]
);

return searchApi;
Expand Down
Loading