Skip to content

Commit a9c2bf6

Browse files
author
Tom Smyth
authored
Merge pull request #585 from thecartercenter/9402_searchbox_filter
9402 advanced search filter
2 parents c8f983f + c435d76 commit a9c2bf6

File tree

24 files changed

+413
-42
lines changed

24 files changed

+413
-42
lines changed

app/assets/javascripts/views/search_form_view.js.coffee

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class ELMO.Views.SearchFormView extends ELMO.Views.ApplicationView
1717

1818
# Add or replace the specified search qualifier
1919
setQualifier: (qualifier, val) ->
20-
search_box = this.$('#search_str')
20+
search_box = this.$('.search-str')
2121
current_search = search_box.val()
2222

2323
# Remove the qualifier text if it's already in the current search

app/assets/stylesheets/global/_search.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
form.search-form {
22
margin-bottom: 10px;
33

4-
input#search_str {
4+
input.search-str {
55
width: 500px;
66
margin-#{$right}: 5px;
77
padding: 3px;

app/assets/stylesheets/local/search/_filters.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
.filters {
22
margin-bottom: 4px;
3+
4+
.btn-toolbar {
5+
margin-bottom: 5px;
6+
}
7+
8+
.btn-margin-left {
9+
margin-left: 5px;
10+
}
311
}
412

513
.filters-popover {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import Button from "react-bootstrap/lib/Button";
4+
5+
import {isQueryParamTruthy} from "./utils";
6+
7+
class AdvancedSearchFilter extends React.Component {
8+
constructor(props) {
9+
super();
10+
this.handleKeyDown = this.handleKeyDown.bind(this);
11+
}
12+
13+
handleKeyDown(event) {
14+
const {onSubmit} = this.props;
15+
16+
if (event.key === "Enter") {
17+
event.preventDefault();
18+
onSubmit();
19+
}
20+
}
21+
22+
render() {
23+
const {advancedSearchText, onChangeAdvancedSearch, onClear, onSubmit} = this.props;
24+
25+
return (
26+
<div>
27+
<input
28+
autoComplete="off"
29+
className="form-control search-str"
30+
name="search"
31+
onChange={onChangeAdvancedSearch}
32+
onKeyDown={this.handleKeyDown}
33+
placeholder={I18n.t("filter.advancedSearch")}
34+
type="text"
35+
value={advancedSearchText} />
36+
<Button
37+
className="btn-apply btn-advanced-search"
38+
onClick={onSubmit}>
39+
{I18n.t("common.search")}
40+
</Button>
41+
{isQueryParamTruthy("search") ? (
42+
<Button
43+
className="btn-clear btn-margin-left"
44+
onClick={onClear}>
45+
{I18n.t("common.clear")}
46+
</Button>
47+
) : null}
48+
</div>
49+
);
50+
}
51+
}
52+
53+
AdvancedSearchFilter.propTypes = {
54+
advancedSearchText: PropTypes.string.isRequired,
55+
onChangeAdvancedSearch: PropTypes.func.isRequired,
56+
onClear: PropTypes.func.isRequired,
57+
onSubmit: PropTypes.func.isRequired,
58+
};
59+
60+
export default AdvancedSearchFilter;

app/javascript/components/search/Filters.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,38 @@ import ButtonToolbar from "react-bootstrap/lib/ButtonToolbar";
44

55
import {CONTROLLER_NAME, getFilterString, submitSearch} from "./utils";
66
import FormFilter from "./FormFilter";
7+
import AdvancedSearchFilter from "./AdvancedSearchFilter";
78

89
class Filters extends React.Component {
910
constructor(props) {
1011
super();
1112

12-
const {selectedFormIds} = props;
13+
const {
14+
selectedFormIds,
15+
advancedSearchText,
16+
} = props;
1317

1418
/*
1519
* The state for all filters is held here.
1620
* Individual filters invoke callbacks to notify this parent component of changes.
1721
*/
18-
this.state = {selectedFormIds};
22+
this.state = {
23+
selectedFormIds,
24+
advancedSearchText,
25+
};
1926

2027
this.handleSubmit = this.handleSubmit.bind(this);
2128
this.handleSelectForm = this.handleSelectForm.bind(this);
2229
this.handleClearFormSelection = this.handleClearFormSelection.bind(this);
30+
this.handleChangeAdvancedSearch = this.handleChangeAdvancedSearch.bind(this);
31+
this.handleClearFilters = this.handleClearFilters.bind(this);
2332
this.renderFilterButtons = this.renderFilterButtons.bind(this);
2433
}
2534

2635
handleSubmit() {
2736
const {allForms} = this.props;
2837
const {selectedFormIds} = this.state;
29-
const filterString = getFilterString(selectedFormIds, allForms);
38+
const filterString = getFilterString(allForms, this.state);
3039
submitSearch(filterString);
3140
}
3241

@@ -38,12 +47,20 @@ class Filters extends React.Component {
3847
this.setState({selectedFormIds: []});
3948
}
4049

50+
handleChangeAdvancedSearch(event) {
51+
this.setState({advancedSearchText: event.target.value});
52+
}
53+
54+
handleClearFilters() {
55+
submitSearch(null);
56+
}
57+
4158
renderFilterButtons() {
4259
const {allForms, selectedFormIds: originalFormIds} = this.props;
4360
const {selectedFormIds} = this.state;
4461

4562
return (
46-
<ButtonToolbar className="filters">
63+
<ButtonToolbar>
4764
<FormFilter
4865
allForms={allForms}
4966
onClearSelection={this.handleClearFormSelection}
@@ -57,23 +74,36 @@ class Filters extends React.Component {
5774

5875
render() {
5976
const {controllerName} = this.props;
77+
const {advancedSearchText} = this.state;
6078
const shouldRenderButtons = controllerName === CONTROLLER_NAME.RESPONSES;
6179

6280
return (
63-
<React.Fragment>
81+
<div className="filters">
6482
{shouldRenderButtons ? this.renderFilterButtons() : null}
65-
</React.Fragment>
83+
84+
<AdvancedSearchFilter
85+
advancedSearchText={advancedSearchText}
86+
onChangeAdvancedSearch={this.handleChangeAdvancedSearch}
87+
onClear={this.handleClearFilters}
88+
onSubmit={this.handleSubmit} />
89+
</div>
6690
);
6791
}
6892
}
6993

7094
Filters.propTypes = {
95+
advancedSearchText: PropTypes.string.isRequired,
7196
allForms: PropTypes.arrayOf(PropTypes.shape({
7297
id: PropTypes.string,
7398
name: PropTypes.string
7499
})).isRequired,
75-
controllerName: PropTypes.string.isRequired,
100+
controllerName: PropTypes.string,
76101
selectedFormIds: PropTypes.arrayOf(PropTypes.string).isRequired
77102
};
78103

104+
Filters.defaultProps = {
105+
// This is expected to be null if the feature flag is disabled.
106+
controllerName: null,
107+
};
108+
79109
export default Filters;

app/javascript/components/search/utils.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import isEmpty from "lodash/isEmpty";
2+
import queryString from "query-string";
23

34
const MAX_HINTS_BEFORE_ELLIPSIZE = 1;
45

@@ -37,12 +38,13 @@ export function getFormNameFromId(allForms, searchId) {
3738
* Given all of the different filter states,
3839
* return a stringified version for the backend.
3940
*/
40-
export function getFilterString(selectedFormIds, allForms) {
41+
export function getFilterString(allForms, {selectedFormIds, advancedSearchText}) {
4142
const selectedFormNames = selectedFormIds
4243
.map((id) => JSON.stringify(getFormNameFromId(allForms, id)));
4344

4445
const parts = [
4546
isEmpty(selectedFormNames) ? null : `form:(${selectedFormNames.join("|")})`,
47+
advancedSearchText,
4648
].filter(Boolean);
4749

4850
return parts.join(" ");
@@ -52,5 +54,20 @@ export function getFilterString(selectedFormIds, allForms) {
5254
* Reload the page with the given search.
5355
*/
5456
export function submitSearch(filterString) {
55-
window.location.assign(`?search=${encodeURIComponent(filterString)}`);
57+
const parsed = queryString.parse(window.location.search);
58+
// The `search` query param will be removed from the URL if it's `undefined`.
59+
const search = filterString || undefined;
60+
const params = queryString.stringify({...parsed, search});
61+
62+
window.location.assign(params
63+
? `?${params}`
64+
: window.location.pathname);
65+
}
66+
67+
/**
68+
* Returns true if the given param name exists and is non-empty.
69+
*/
70+
export function isQueryParamTruthy(paramName) {
71+
const parsed = queryString.parse(window.location.search);
72+
return Boolean(parsed[paramName]);
5673
}

app/views/searches/_form.html.erb

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<form class="search-form form-inline">
22
<%= react_component("search/Filters", {
33
controllerName: Settings.filters_beta.present? ? controller_name.to_json : nil,
4-
allForms: [
4+
advancedSearchText: params[:search] || "",
5+
allForms: Settings.filters_beta.present? ? [
56
{
67
id: "1",
78
name: "Form A"
@@ -14,16 +15,9 @@
1415
id: "3",
1516
name: "Q\"uo'te`s"
1617
}
17-
],
18-
selectedFormIds: ["2"]
18+
] : [],
19+
selectedFormIds: Settings.filters_beta.present? ? ["2"] : []
1920
}) %>
20-
<input type="text" class="form-control" name="search"
21-
value="<%= params[:search] %>" id="search_str" placeholder="<%= t("common.search") %>" autocomplete="off">
22-
<input type="submit" value="<%= t("common.search") %>" class="btn btn-default">
23-
<% if params[:search].present? %>
24-
<input type="button" value="<%= t("common.clear") %>" class="btn btn-default btn-clear">
25-
<% end %>
26-
<br/>
2721
<div class="search-footer">
2822
<%= search_examples %>
2923
<a href="#"><%= t('search.help_link') %></a>

config/locales/en/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,7 @@ en:
12841284
filter:
12851285
form: "Form"
12861286
chooseForm: "Choose a form"
1287+
advancedSearch: "Advanced search"
12871288

12881289
form:
12891290
select_api_users: "Select all users who can access form data via the API:"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"jquery": "3.3.1",
2020
"lodash": "4.17.11",
2121
"prop-types": "^15.6.1",
22+
"query-string": "6.3.0",
2223
"react": "^16.4.1",
2324
"react-bootstrap": "0.32.4",
2425
"react-dom": "^16.4.1",

spec/features/forms/question/question_index_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
visit("/en/m/#{mission.compact_name}/questions")
2323

2424
# do a search
25-
fill_in "search_str", with: "dup"
25+
fill_in class: "search-str", with: "dup"
2626
click_on "Search"
2727

2828
# clear search box

0 commit comments

Comments
 (0)