Initial coomit for AAI-UI(sparky-fe)
[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           }}
127           renderSuggestion={this.renderSuggestion}
128           inputProps={inputProps}
129           focusFirstSuggestion={false}
130           renderSuggestionsContainer={this.renderSuggestionsContainer}/>
131         <ButtonGroup className='auto-complete-search-button-group'>
132           <Button type='submit' className={clearButtonClass}
133                   onClick={onClearSuggestionsTextFieldRequested}>
134             <i className={ICON_CLASS_CLEAR} aria-hidden='true'/>
135           </Button>
136
137           <Button type='submit' className='auto-complete-search-button' onClick={() => {
138             this.newSearchSelected(this.getSelectedSuggestionObj(value, cachedSuggestions),
139               onInvalidSearch, dispatchAnalytics, value);
140           }}>
141             <i className={ICON_CLASS_SEARCH} aria-hidden='true'/>
142           </Button>
143         </ButtonGroup>
144       </div>
145     );
146   }
147
148   renderSuggestion(suggestion, {query}) {
149     let toHighLightArray = query.split(' ');
150     let suggestionTextArray = suggestion.text.split(' ');
151     let arrayIndex = 0;
152
153     if (suggestion.text !== NO_MATCHES_FOUND) {
154       // render the suggestion as a clickable link
155       return (
156         <div className='suggestionFlexContainer'>
157         <span key={'sugSpan1'}
158               className='suggestionColumnTwo'>
159           <Link style={{textDecoration: 'none'}}
160                 to={'/' + suggestion.route + '/' + suggestion.hashId}
161                 replace={true}>
162             {suggestionTextArray.map(
163               function () {
164                 return (
165                   <span key={arrayIndex + 'sugSpan3'}>
166                     <Highlighter key={arrayIndex + 'high'}
167                                  highlightClassName='highlight'
168                                  searchWords={toHighLightArray}
169                                  textToHighlight={suggestionTextArray[arrayIndex]}/>
170                     { ++arrayIndex ? ' ' : ' '}
171                  </span>);
172
173               })} </Link>
174       </span>
175         </div>
176       );
177     } else {
178       // render the suggestion as plain text
179       return (
180         <div className='suggestionFlexContainer'>
181           <span key={'sugSpan1'}
182                 className='suggestionColumnTwo'>
183               {suggestionTextArray.map(
184                 function () {
185                   return (
186                     <span key={arrayIndex + 'sugSpan3'}>
187                       <Highlighter key={arrayIndex + 'high'}
188                                    highlightClassName='highlight'
189                                    searchWords={toHighLightArray}
190                                    textToHighlight={suggestionTextArray[arrayIndex]}/>
191                       { ++arrayIndex ? ' ' : ' '}
192                    </span>);
193
194                 })}
195           </span>
196         </div>
197       );
198     }
199   }
200
201   renderSuggestionsContainer({children, ...rest}) {
202     if (children !== null && children.props.items.length < 5) {
203       rest.className = 'react-autosuggest__suggestions-containerCopy';
204     }
205     return (
206       <div {...rest}>
207         {children}
208       </div>
209     );
210   }
211 }