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