[AAI-92 Amsterdam] Update license
[aai/sparky-fe.git] / src / generic-components / autoCompleteSearchBar / AutoCompleteSearchBar.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 import React, {Component} from 'react';
24 import {Button} from 'react-bootstrap';
25 import AutoSuggest from 'react-autosuggest';
26 import Highlighter from 'react-highlight-words';
27 import debounce from 'lodash.debounce';
28 import {ButtonGroup} from 'react-bootstrap';
29 import {Link} from 'react-router-dom';
30
31 import {changeUrlAddress} from 'utils/Routes.js';
32
33 import {
34   ICON_CLASS_SEARCH,
35   ICON_CLASS_CLEAR,
36   SEARCH_DEBOUNCE_TIME,
37   NO_MATCHES_FOUND,
38   SEARCH_PLACEHOLDER_TEXT
39 } from './AutoCompleteSearchBarConstants.js';
40
41 export default class AutoCompleteSearchBar extends Component {
42   static propTypes = {
43     value: React.PropTypes.string,
44     suggestions: React.PropTypes.array,
45     cachedSuggestions: React.PropTypes.array,
46     suggestionName: React.PropTypes.string
47   };
48
49   componentWillMount() {
50     this.debouncedLoadSuggestions =
51       debounce(this.props.onSuggestionsFetchRequested, SEARCH_DEBOUNCE_TIME);
52   }
53
54   onSuggestionsFetchRequested = ({value}) => {
55     this.debouncedLoadSuggestions({value});
56   };
57
58   isValidSearch(value) {
59     return (value && value !== NO_MATCHES_FOUND);
60   }
61
62   isValidSuggestionObject(suggestionObj) {
63     return (suggestionObj &&
64     Object.keys(suggestionObj).length > 0 &&
65     this.isValidSearch(suggestionObj.text));
66   }
67
68   getSelectedSuggestionObj(value, cachedSuggestions) {
69     let matchesSuggestion = {};
70
71     if (this.isValidSearch(value)) {
72       for (let suggestionIndex in cachedSuggestions) {
73         if (cachedSuggestions[suggestionIndex].text === value) {
74           matchesSuggestion = cachedSuggestions[suggestionIndex];
75           break;
76         }
77       }
78     }
79
80     return matchesSuggestion;
81   }
82
83   newSearchSelected(
84     cachedSuggestion, invalidSearchCallback, dispatchAnalytics, value) {
85     if (this.isValidSuggestionObject(cachedSuggestion)) {
86       changeUrlAddress(cachedSuggestion, this.props.history);
87       //Call analytics if defined
88       if (dispatchAnalytics) {
89         dispatchAnalytics();
90       }
91     } else {
92       invalidSearchCallback(value);
93     }
94   }
95
96   render() {
97     const {
98             value, suggestions,
99             suggestionName, cachedSuggestions,
100             onInputChange, onInvalidSearch,
101             onClearSuggestionsTextFieldRequested,
102             onSuggestionsClearRequested,
103             dispatchAnalytics
104           } = this.props;
105     const inputProps = {
106       placeholder: SEARCH_PLACEHOLDER_TEXT,
107       value,
108       onChange: onInputChange
109     };
110
111     let clearButtonClass = (value.length > 0)
112       ? 'auto-complete-clear-button'
113       : 'auto-complete-clear-button hidden';
114     return (
115       <div className='auto-complete-search'>
116         <AutoSuggest
117           suggestions={suggestions}
118           getSuggestionValue={suggestion => suggestion[suggestionName]}
119           onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
120           onSuggestionsClearRequested={onSuggestionsClearRequested}
121           onSuggestionSelected={(event, {suggestion}) => {
122             this.newSearchSelected(suggestion, onInvalidSearch, dispatchAnalytics, value);
123           }}
124           renderSuggestion={this.renderSuggestion}
125           inputProps={inputProps}
126           focusFirstSuggestion={false}
127           renderSuggestionsContainer={this.renderSuggestionsContainer}/>
128         <ButtonGroup className='auto-complete-search-button-group'>
129           <Button type='submit' className={clearButtonClass}
130                   onClick={onClearSuggestionsTextFieldRequested}>
131             <i className={ICON_CLASS_CLEAR} aria-hidden='true'/>
132           </Button>
133
134           <Button type='submit' className='auto-complete-search-button' onClick={() => {
135             this.newSearchSelected(this.getSelectedSuggestionObj(value, cachedSuggestions),
136               onInvalidSearch, dispatchAnalytics, value);
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 }