diff --git a/ui/src/Components/NavBar/FilterInput/index.js b/ui/src/Components/NavBar/FilterInput/index.js index 9b5e354ff..e93c79e21 100644 --- a/ui/src/Components/NavBar/FilterInput/index.js +++ b/ui/src/Components/NavBar/FilterInput/index.js @@ -9,6 +9,8 @@ import debounce from "lodash/debounce"; import Autosuggest from "react-autosuggest"; import Highlight from "react-highlighter"; +import onClickOutside from "react-onclickoutside"; + import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSearch } from "@fortawesome/free-solid-svg-icons/faSearch"; @@ -20,191 +22,200 @@ import { FilterInputLabel } from "Components/Labels/FilterInputLabel"; import { AutosuggestTheme } from "./Constants"; import { History } from "./History"; -const FilterInput = observer( - class FilterInput extends Component { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired - }; +const FilterInput = onClickOutside( + observer( + class FilterInput extends Component { + static propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + settingsStore: PropTypes.instanceOf(Settings).isRequired + }; - inputStore = observable( - { - ref: null, - suggestions: [], - suggestionsFetch: null, - value: "", - focused: false, - storeInputReference(ref) { - this.ref = ref; + inputStore = observable( + { + ref: null, + suggestions: [], + suggestionsFetch: null, + value: "", + focused: false, + storeInputReference(ref) { + this.ref = ref; + } + }, + { + storeInputReference: action.bound + }, + { name: "Filter input state" } + ); + + componentDidMount() { + if (this.inputStore.ref !== null && !IsMobile()) { + this.inputStore.ref.input.focus(); } - }, - { - storeInputReference: action.bound - }, - { name: "Filter input state" } - ); - - componentDidMount() { - if (this.inputStore.ref !== null && !IsMobile()) { - this.inputStore.ref.input.focus(); } - } - onChange = action((event, { newValue, method }) => { - // onChange here handles change for the user input in the filter bar - // we need to update inputStore.value every time user types in something - event.preventDefault(); - this.inputStore.value = newValue; - }); + onChange = action((event, { newValue, method }) => { + // onChange here handles change for the user input in the filter bar + // we need to update inputStore.value every time user types in something + event.preventDefault(); + this.inputStore.value = newValue; + }); - onSubmit = action(event => { - event.preventDefault(); - if (this.inputStore.value !== "") { - this.props.alertStore.filters.addFilter(this.inputStore.value); - this.inputStore.value = ""; - } - }); + onSubmit = action(event => { + event.preventDefault(); + if (this.inputStore.value !== "") { + this.props.alertStore.filters.addFilter(this.inputStore.value); + this.inputStore.value = ""; + } + }); - onSuggestionsClearRequested = action(() => { - this.inputStore.suggestions = []; - }); + onSuggestionsClearRequested = action(() => { + this.inputStore.suggestions = []; + }); - onSuggestionsFetchRequested = debounce( - action(({ value }) => { - if (value !== "") { - this.inputStore.suggestionsFetch = FetchGet( - FormatBackendURI(`autocomplete.json?term=${value}`), - {} - ) - .then( - result => result.json(), - err => { - return []; - } + onSuggestionsFetchRequested = debounce( + action(({ value }) => { + if (value !== "") { + this.inputStore.suggestionsFetch = FetchGet( + FormatBackendURI(`autocomplete.json?term=${value}`), + {} ) - .then(result => result.slice(0, 20)) - .then(result => { - this.inputStore.suggestions = result; - }) - .catch(err => console.error(err.message)); - } - }), - 300 - ); - - onSuggestionSelected = action((event, { suggestion }) => { - this.inputStore.value = ""; - this.props.alertStore.filters.addFilter(suggestion); - }); - - onInputClick = (inputReference, event) => { - if ( - typeof event.target.className === "string" && - event.target.className.split(" ").includes("form-control") - ) { - inputReference.input.focus(); - } - }; - - onFocus = action(() => { - this.inputStore.focused = true; - }); - - onBlur = action(() => { - this.inputStore.focused = false; - }); - - renderSuggestion = (suggestion, { query, isHighlighted }) => { - return ( - - {suggestion} - - ); - }; - - renderInputComponent = inputProps => { - const { value } = inputProps; - return ( - - ); - }; - - render() { - const { alertStore, settingsStore } = this.props; - - return ( - // data-filters is there to register filters for observation in mobx - // in order to re-render input component -
f.raw).join(" ")} - > -
-
- - - -
-
{ - this.onInputClick(this.inputStore.ref, event); - }} - onFocus={this.onFocus} - onBlur={this.onBlur} - > - {alertStore.filters.values.map(filter => ( - - ))} - - value && value.trim().length > 1 + .then( + result => result.json(), + err => { + return []; } - getSuggestionValue={suggestion => suggestion} - renderSuggestion={this.renderSuggestion} - renderInputComponent={this.renderInputComponent} - inputProps={{ - value: this.inputStore.value, - onChange: this.onChange - }} - theme={AutosuggestTheme} - /> -
+ ) + .then(result => result.slice(0, 20)) + .then(result => { + this.inputStore.suggestions = result; + }) + .catch(err => console.error(err.message)); + } + }), + 300 + ); + + onSuggestionSelected = action((event, { suggestion }) => { + this.inputStore.value = ""; + this.props.alertStore.filters.addFilter(suggestion); + }); + + onInputClick = (inputReference, event) => { + if ( + typeof event.target.className === "string" && + event.target.className.split(" ").includes("form-control") + ) { + inputReference.input.focus(); + } + }; + + onFocus = action(() => { + this.inputStore.focused = true; + }); + + onBlur = action(() => { + this.inputStore.focused = false; + }); + + handleClickOutside = action(event => { + this.inputStore.focused = false; + }); + + renderSuggestion = (suggestion, { query, isHighlighted }) => { + return ( + + {suggestion} + + ); + }; + + renderInputComponent = inputProps => { + const { value } = inputProps; + return ( + + ); + }; + + render() { + const { alertStore, settingsStore } = this.props; + + return ( + // data-filters is there to register filters for observation in mobx + // in order to re-render input component + f.raw).join(" ")} + >
- +
+ + + +
+
{ + this.onInputClick(this.inputStore.ref, event); + }} + onFocus={this.onFocus} + onBlur={this.onBlur} + > + {alertStore.filters.values.map(filter => ( + + ))} + + value && value.trim().length > 1 + } + getSuggestionValue={suggestion => suggestion} + renderSuggestion={this.renderSuggestion} + renderInputComponent={this.renderInputComponent} + inputProps={{ + value: this.inputStore.value, + onChange: this.onChange + }} + theme={AutosuggestTheme} + /> +
+
+ +
-
-
- ); + + ); + } } - } + ) ); export { FilterInput }; diff --git a/ui/src/Components/NavBar/FilterInput/index.test.js b/ui/src/Components/NavBar/FilterInput/index.test.js index c141ec6dc..f9c73baf2 100644 --- a/ui/src/Components/NavBar/FilterInput/index.test.js +++ b/ui/src/Components/NavBar/FilterInput/index.test.js @@ -135,6 +135,12 @@ describe("", () => { expect(toDiffableHtml(tree.html())).not.toMatch(/bg-focused/); }); + it("calling handleClickOutside() changes background color", () => { + const tree = MountedInput(); + tree.instance().handleClickOutside(); + expect(toDiffableHtml(tree.html())).not.toMatch(/bg-focused/); + }); + it("componentDidMount executes even when inputStore.ref=null", () => { const tree = MountedInput(); const instance = tree.instance();