Adding filter bar
[aai/sparky-fe.git] / src / generic-components / autoCompleteSearchBar / AutoCompleteSearchBar.jsx
1 /*
2  * ============LICENSE_START===================================================
3  * SPARKY (AAI UI service)
4  * ============================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
8  * ============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=====================================================
21  *
22  * ECOMP and OpenECOMP are trademarks
23  * and service marks of AT&T Intellectual Property.
24  */
25
26 import React, {Component} from 'react';
27 import {Button} from 'react-bootstrap';
28 import AutoSuggest from 'react-autosuggest';
29 import Highlighter from 'react-highlight-words';
30 import debounce from 'lodash.debounce';
31 import {ButtonGroup} from 'react-bootstrap';
32 import {Link} from 'react-router-dom';
33
34 import {changeUrlAddress} from 'utils/Routes.js';
35
36 import {
37   ICON_CLASS_SEARCH,
38   ICON_CLASS_CLEAR,
39   SEARCH_DEBOUNCE_TIME,
40   NO_MATCHES_FOUND,
41   SEARCH_PLACEHOLDER_TEXT
42 } from './AutoCompleteSearchBarConstants.js';
43
44 export default class AutoCompleteSearchBar extends Component {
45   static propTypes = {
46     value: React.PropTypes.string,
47     suggestions: React.PropTypes.array,
48     cachedSuggestions: React.PropTypes.array,
49     suggestionName: React.PropTypes.string
50   };
51
52   componentWillMount() {
53     this.debouncedLoadSuggestions =
54       debounce(this.props.onSuggestionsFetchRequested, SEARCH_DEBOUNCE_TIME);
55   }
56
57   onSuggestionsFetchRequested = ({value}) => {
58     this.debouncedLoadSuggestions({value});
59   };
60
61   isValidSearch(value) {
62     return (value && value !== NO_MATCHES_FOUND);
63   }
64
65   isValidSuggestionObject(suggestionObj) {
66     return (suggestionObj &&
67     Object.keys(suggestionObj).length > 0 &&
68     this.isValidSearch(suggestionObj.text));
69   }
70
71   getSelectedSuggestionObj(value, cachedSuggestions) {
72     let matchesSuggestion = {};
73
74     if (this.isValidSearch(value)) {
75       for (let suggestionIndex in cachedSuggestions) {
76         if (cachedSuggestions[suggestionIndex].text === value) {
77           matchesSuggestion = cachedSuggestions[suggestionIndex];
78           break;
79         }
80       }
81     }
82
83     return matchesSuggestion;
84   }
85
86   newSearchSelected(
87     cachedSuggestion, invalidSearchCallback, dispatchAnalytics, value) {
88     if (this.isValidSuggestionObject(cachedSuggestion)) {
89       changeUrlAddress(cachedSuggestion, this.props.history);
90       //Call analytics if defined
91       if (dispatchAnalytics) {
92         dispatchAnalytics();
93       }
94     } else {
95       invalidSearchCallback(value);
96     }
97   }
98
99   render() {
100     const {
101             value, suggestions,
102             suggestionName, cachedSuggestions,
103             onInputChange, onInvalidSearch,
104             onClearSuggestionsTextFieldRequested,
105             onSuggestionsClearRequested,
106             dispatchAnalytics
107           } = this.props;
108     const inputProps = {
109       placeholder: SEARCH_PLACEHOLDER_TEXT,
110       value,
111       onChange: onInputChange
112     };
113
114     let clearButtonClass = (value.length > 0)
115       ? 'auto-complete-clear-button'
116       : 'auto-complete-clear-button hidden';
117     return (
118       <div className='auto-complete-search'>
119         <AutoSuggest
120           suggestions={suggestions}
121           getSuggestionValue={suggestion => suggestion[suggestionName]}
122           onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
123           onSuggestionsClearRequested={onSuggestionsClearRequested}
124           onSuggestionSelected={(event, {suggestion}) => {
125             this.newSearchSelected(suggestion, onInvalidSearch, dispatchAnalytics, value);
126             this.props.onClearSuggestionsTextFieldRequested();
127           }}
128           renderSuggestion={this.renderSuggestion}
129           inputProps={inputProps}
130           focusFirstSuggestion={false}
131           renderSuggestionsContainer={this.renderSuggestionsContainer}/>
132         <ButtonGroup className='auto-complete-search-button-group'>
133           <Button type='submit' className={clearButtonClass}
134                   onClick={onClearSuggestionsTextFieldRequested}>
135             <i className={ICON_CLASS_CLEAR} aria-hidden='true'/>
136           </Button>
137
138           <Button type='submit' className='auto-complete-search-button' onClick={() => {
139             this.newSearchSelected(this.getSelectedSuggestionObj(value, cachedSuggestions),
140               onInvalidSearch, dispatchAnalytics, value);
141             this.props.onSuggestionsClearRequested();
142           }}>
143             <i className={ICON_CLASS_SEARCH} aria-hidden='true'/>
144           </Button>
145         </ButtonGroup>
146       </div>
147     );
148   }
149
150   renderSuggestion(suggestion, {query}) {
151     let toHighLightArray = query.split(' ');
152     let suggestionTextArray = suggestion.text.split(' ');
153     let arrayIndex = 0;
154
155     if (suggestion.text !== NO_MATCHES_FOUND) {
156       // render the suggestion as a clickable link
157       return (
158         <div className='suggestionFlexContainer'>
159         <span key={'sugSpan1'}
160               className='suggestionColumnTwo'>
161           <Link style={{textDecoration: 'none'}}
162                 to={'/' + suggestion.route + '/' + suggestion.hashId}
163                 replace={true}>
164             {suggestionTextArray.map(
165               function () {
166                 return (
167                   <span key={arrayIndex + 'sugSpan3'}>
168                     <Highlighter key={arrayIndex + 'high'}
169                                  highlightClassName='highlight'
170                                  searchWords={toHighLightArray}
171                                  textToHighlight={suggestionTextArray[arrayIndex]}/>
172                     { ++arrayIndex ? ' ' : ' '}
173                  </span>);
174
175               })} </Link>
176       </span>
177         </div>
178       );
179     } else {
180       // render the suggestion as plain text
181       return (
182         <div className='suggestionFlexContainer'>
183           <span key={'sugSpan1'}
184                 className='suggestionColumnTwo'>
185               {suggestionTextArray.map(
186                 function () {
187                   return (
188                     <span key={arrayIndex + 'sugSpan3'}>
189                       <Highlighter key={arrayIndex + 'high'}
190                                    highlightClassName='highlight'
191                                    searchWords={toHighLightArray}
192                                    textToHighlight={suggestionTextArray[arrayIndex]}/>
193                       { ++arrayIndex ? ' ' : ' '}
194                    </span>);
195
196                 })}
197           </span>
198         </div>
199       );
200     }
201   }
202
203   renderSuggestionsContainer({children, ...rest}) {
204     if (children !== null && children.props.items.length < 5) {
205       rest.className = 'react-autosuggest__suggestions-containerCopy';
206     }
207     return (
208       <div {...rest}>
209         {children}
210       </div>
211     );
212   }
213 }