Skip to content

Commit 055e951

Browse files
committed
Updated EuiSearchBar to better match the controlled component pattern, added example
1 parent 57631a2 commit 055e951

File tree

8 files changed

+391
-95
lines changed

8 files changed

+391
-95
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## [`master`](https://github.com/elastic/eui/tree/master)
22

3-
- When using `EuiSearchBar` as a controlled input, the `onParse` and `onChange` callbacks will now fire
3+
**Breaking changes**
4+
5+
- `EuiSearchBar` no longer has an `onParse` callback, and now passes an object to `onChange` with the shape `{ query, queryText, error }` ([#863](https://github.com/elastic/eui/pull/863))
46

57
## [`0.0.49`](https://github.com/elastic/eui/tree/v0.0.49)
68

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
import React, { Component, Fragment } from 'react';
2+
import { times } from 'lodash';
3+
import { Random } from '../../../../src/services/random';
4+
import {
5+
EuiHealth,
6+
EuiCallOut,
7+
EuiSpacer,
8+
EuiFlexGroup,
9+
EuiFlexItem,
10+
EuiSwitch,
11+
EuiBasicTable,
12+
EuiSearchBar,
13+
EuiButton,
14+
} from '../../../../src/components';
15+
16+
const random = new Random();
17+
18+
const tags = [
19+
{ name: 'marketing', color: 'danger' },
20+
{ name: 'finance', color: 'success' },
21+
{ name: 'eng', color: 'success' },
22+
{ name: 'sales', color: 'warning' },
23+
{ name: 'ga', color: 'success' }
24+
];
25+
26+
const types = [
27+
'dashboard',
28+
'visualization',
29+
'watch',
30+
];
31+
32+
const users = [
33+
'dewey',
34+
'wanda',
35+
'carrie',
36+
'jmack',
37+
'gabic',
38+
];
39+
40+
const items = times(10, (id) => {
41+
return {
42+
id,
43+
status: random.oneOf(['open', 'closed']),
44+
type: random.oneOf(types),
45+
tag: random.setOf(tags.map(tag => tag.name), { min: 0, max: 3 }),
46+
active: random.boolean(),
47+
owner: random.oneOf(users),
48+
followers: random.integer({ min: 0, max: 20 }),
49+
comments: random.integer({ min: 0, max: 10 }),
50+
stars: random.integer({ min: 0, max: 5 })
51+
};
52+
});
53+
54+
const loadTags = () => {
55+
return new Promise((resolve) => {
56+
setTimeout(() => {
57+
resolve(tags.map(tag => ({
58+
value: tag.name,
59+
view: <EuiHealth color={tag.color}>{tag.name}</EuiHealth>
60+
})));
61+
}, 2000);
62+
});
63+
};
64+
65+
const initialQuery = EuiSearchBar.Query.MATCH_ALL;
66+
67+
export class ControlledSearchBar extends Component {
68+
69+
constructor(props) {
70+
super(props);
71+
this.state = {
72+
query: initialQuery,
73+
result: items,
74+
error: null,
75+
incremental: false
76+
};
77+
}
78+
79+
onChange = ({ query, error }) => {
80+
if (error) {
81+
this.setState({ error });
82+
} else {
83+
this.setState({
84+
error: null,
85+
result: EuiSearchBar.Query.execute(query, items, { defaultFields: ['owner', 'tag', 'type'] }),
86+
query
87+
});
88+
}
89+
};
90+
91+
toggleIncremental = () => {
92+
this.setState(prevState => ({ incremental: !prevState.incremental }));
93+
};
94+
95+
setQuery = query => {
96+
this.setState({ query });
97+
}
98+
99+
renderBookmarks() {
100+
return (
101+
<Fragment>
102+
<p>Enter a query, or select one from a bookmark</p>
103+
<EuiSpacer size="s"/>
104+
<EuiFlexGroup>
105+
<EuiFlexItem grow={false}>
106+
<EuiButton size="s" onClick={() => this.setQuery('status:open owner:dewey')}>mine, open</EuiButton>
107+
</EuiFlexItem>
108+
<EuiFlexItem grow={false}>
109+
<EuiButton size="s" onClick={() => this.setQuery('status:closed owner:dewey')}>mine, closed</EuiButton>
110+
</EuiFlexItem>
111+
</EuiFlexGroup>
112+
<EuiSpacer size="m"/>
113+
</Fragment>
114+
);
115+
}
116+
117+
renderSearch() {
118+
const { incremental } = this.state;
119+
120+
const filters = [
121+
{
122+
type: 'field_value_toggle_group',
123+
field: 'status',
124+
items: [
125+
{
126+
value: 'open',
127+
name: 'Open'
128+
},
129+
{
130+
value: 'closed',
131+
name: 'Closed'
132+
}
133+
]
134+
},
135+
{
136+
type: 'is',
137+
field: 'active',
138+
name: 'Active',
139+
negatedName: 'Inactive'
140+
},
141+
{
142+
type: 'field_value_toggle',
143+
name: 'Mine',
144+
field: 'owner',
145+
value: 'dewey'
146+
},
147+
{
148+
type: 'field_value_selection',
149+
field: 'tag',
150+
name: 'Tag',
151+
multiSelect: 'or',
152+
cache: 10000, // will cache the loaded tags for 10 sec
153+
options: () => loadTags()
154+
}
155+
];
156+
157+
const schema = {
158+
strict: true,
159+
fields: {
160+
active: {
161+
type: 'boolean'
162+
},
163+
status: {
164+
type: 'string'
165+
},
166+
followers: {
167+
type: 'number'
168+
},
169+
comments: {
170+
type: 'number'
171+
},
172+
stars: {
173+
type: 'number'
174+
},
175+
created: {
176+
type: 'date'
177+
},
178+
owner: {
179+
type: 'string'
180+
},
181+
tag: {
182+
type: 'string',
183+
validate: (value) => {
184+
if (!tags.some(tag => tag.name === value)) {
185+
throw new Error(`unknown tag (possible values: ${tags.map(tag => tag.name).join(',')})`);
186+
}
187+
}
188+
}
189+
}
190+
};
191+
192+
return (
193+
<EuiSearchBar
194+
query={this.state.query}
195+
box={{
196+
placeholder: 'e.g. type:visualization -is:active joe',
197+
incremental,
198+
schema
199+
}}
200+
filters={filters}
201+
onChange={this.onChange}
202+
/>
203+
);
204+
}
205+
206+
renderError() {
207+
const { error } = this.state;
208+
if (!error) {
209+
return;
210+
}
211+
return (
212+
<Fragment>
213+
<EuiCallOut
214+
iconType="faceSad"
215+
color="danger"
216+
title={`Invalid search: ${error.message}`}
217+
/>
218+
<EuiSpacer size="l"/>
219+
</Fragment>
220+
);
221+
}
222+
223+
renderTable() {
224+
const columns = [
225+
{
226+
name: 'Type',
227+
field: 'type'
228+
},
229+
{
230+
name: 'Open',
231+
field: 'status',
232+
render: (status) => status === 'open' ? 'Yes' : 'No'
233+
},
234+
{
235+
name: 'Active',
236+
field: 'active',
237+
dataType: 'boolean'
238+
},
239+
{
240+
name: 'Tags',
241+
field: 'tag'
242+
},
243+
{
244+
name: 'Owner',
245+
field: 'owner'
246+
},
247+
{
248+
name: 'Stats',
249+
width: '150px',
250+
render: (item) => {
251+
return (
252+
<div>
253+
<div>{`${item.stars} Stars`}</div>
254+
<div>{`${item.followers} Followers`}</div>
255+
<div>{`${item.comments} Comments`}</div>
256+
</div>
257+
);
258+
}
259+
}
260+
];
261+
262+
const queriedItems = EuiSearchBar.Query.execute(this.state.query, items, {
263+
defaultFields: ['owner', 'tag', 'type']
264+
});
265+
266+
return (
267+
<EuiBasicTable
268+
items={queriedItems}
269+
columns={columns}
270+
/>
271+
);
272+
}
273+
274+
render() {
275+
const {
276+
incremental,
277+
} = this.state;
278+
279+
const content = this.renderError() || (
280+
<EuiFlexGroup>
281+
<EuiFlexItem grow={6}>
282+
{this.renderTable()}
283+
</EuiFlexItem>
284+
</EuiFlexGroup>
285+
);
286+
287+
return (
288+
<Fragment>
289+
<EuiFlexGroup>
290+
<EuiFlexItem>
291+
{this.renderBookmarks()}
292+
</EuiFlexItem>
293+
</EuiFlexGroup>
294+
<EuiFlexGroup alignItems="center">
295+
<EuiFlexItem>
296+
{this.renderSearch()}
297+
</EuiFlexItem>
298+
299+
<EuiFlexItem grow={false}>
300+
<EuiSwitch
301+
label="Incremental"
302+
checked={incremental}
303+
onChange={this.toggleIncremental}
304+
/>
305+
</EuiFlexItem>
306+
</EuiFlexGroup>
307+
<EuiSpacer size="l"/>
308+
{content}
309+
</Fragment>
310+
);
311+
}
312+
}

src-docs/src/views/search_bar/props_info.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@ export const propsInfo = {
44
__docgenInfo: {
55
props: {
66
onChange: {
7-
description: 'Called every time the query behind the search bar changes',
8-
required: true,
9-
type: { name: '(query: #Query) => void' }
10-
},
11-
onParse: {
127
description: 'Called every time the text query in the search box is parsed. When parsing is successful ' +
138
'the callback will receive both the query text and the parsed query. When it fails ' +
149
'the callback ill receive the query text and an error object (holding the error message)',
15-
required: false,
10+
required: true,
1611
type: { name: '({ query?: #Query, queryText: string, error?: { message: string } }) => void' }
1712
},
1813
query: {

src-docs/src/views/search_bar/search_bar.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,16 @@ export class SearchBar extends Component {
7777
};
7878
}
7979

80-
onParse = ({ error }) => {
81-
this.setState({ error });
82-
};
83-
84-
onChange = (query) => {
85-
this.setState({
86-
error: null,
87-
result: EuiSearchBar.Query.execute(query, items, { defaultFields: ['owner', 'tag', 'type'] }),
88-
query
89-
});
80+
onChange = ({ query, error }) => {
81+
if (error) {
82+
this.setState({ error });
83+
} else {
84+
this.setState({
85+
error: null,
86+
result: EuiSearchBar.Query.execute(query, items, { defaultFields: ['owner', 'tag', 'type'] }),
87+
query
88+
});
89+
}
9090
};
9191

9292
toggleIncremental = () => {
@@ -178,7 +178,6 @@ export class SearchBar extends Component {
178178
}}
179179
filters={filters}
180180
onChange={this.onChange}
181-
onParse={this.onParse}
182181
/>
183182
);
184183
}

0 commit comments

Comments
 (0)