[AAI] Remove Robby Maharajh & Harish Kajur as committers
[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 { PropTypes } from 'prop-types';
23 import {Button} from 'react-bootstrap';
24 import AutoSuggest from 'react-autosuggest';
25 import Highlighter from 'react-highlight-words';
26 import debounce from 'lodash.debounce';
27 import {ButtonGroup} from 'react-bootstrap';
28 import Modal from 'react-bootstrap/lib/Modal';
29 import {Link} from 'react-router-dom';
30 import {genericRequest} from 'app/networking/NetworkCalls.js';
31
32 import {changeUrlAddress} from 'utils/Routes.js';
33
34 import {
35   ICON_CLASS_SEARCH,
36   ICON_CLASS_CLEAR,
37   ICON_CLASS_HELP,
38   SEARCH_DEBOUNCE_TIME,
39   NO_MATCHES_FOUND,
40   SEARCH_PLACEHOLDER_TEXT
41 } from './AutoCompleteSearchBarConstants.js';
42
43 export default class AutoCompleteSearchBar extends Component {
44   static propTypes = {
45     value: PropTypes.string,
46     suggestions: PropTypes.array,
47     cachedSuggestions: PropTypes.array,
48     suggestionName: PropTypes.string
49   };
50
51   constructor(props) {
52     console.log(props);
53     super(props);
54     this.state = {
55       helpModalShow: false,
56       searchable: []
57     };
58   };
59
60   componentWillMount() {
61     this.debouncedLoadSuggestions =
62       debounce(this.props.onSuggestionsFetchRequested, SEARCH_DEBOUNCE_TIME);
63   }
64
65   onSuggestionsFetchRequested = ({value}) => {
66     this.debouncedLoadSuggestions({value});
67   };
68
69   isValidSearch(value) {
70     return (value && value !== NO_MATCHES_FOUND);
71   }
72
73   isValidSuggestionObject(suggestionObj) {
74     return (suggestionObj &&
75     Object.keys(suggestionObj).length > 0 &&
76     this.isValidSearch(suggestionObj.text));
77   }
78
79   getSelectedSuggestionObj(value, cachedSuggestions) {
80     let matchesSuggestion = {};
81
82     if (this.isValidSearch(value)) {
83       for (let suggestionIndex in cachedSuggestions) {
84         if (cachedSuggestions[suggestionIndex].text === value) {
85           matchesSuggestion = cachedSuggestions[suggestionIndex];
86           break;
87         }
88       }
89     }
90
91     return matchesSuggestion;
92   }
93
94   newSearchSelected(
95     cachedSuggestion, invalidSearchCallback, dispatchAnalytics, value) {
96     if (this.isValidSuggestionObject(cachedSuggestion)) {
97       changeUrlAddress(cachedSuggestion, this.props.history);
98       //Call analytics if defined
99       if (dispatchAnalytics) {
100         dispatchAnalytics();
101       }
102     } else {
103       invalidSearchCallback(value);
104     }
105   }
106
107   render() {
108     const {
109       value, suggestions,
110       suggestionName, cachedSuggestions,
111       onInputChange, onInvalidSearch,
112       onClearSuggestionsTextFieldRequested,
113       onSuggestionsClearRequested,
114       dispatchAnalytics
115     } = this.props;
116     const inputProps = {
117       placeholder: SEARCH_PLACEHOLDER_TEXT,
118       value,
119       onChange: onInputChange
120     };
121
122     let closeHelpModal = () => {
123       this.setState({helpModalShow: false});
124     };
125     let showHelpModal = () => {
126       genericRequest('/schema/searchable', true, 'GET').then(res=>{
127         let searchDOM =  res.sort(function(a, b) {
128           var compareA = (a['node-type']).toLowerCase();
129           var compareB = (b['node-type']).toLowerCase();
130           if(compareA < compareB){
131             return -1;
132           };
133           if(compareA > compareB){
134             return 1;
135           };
136           return 0;
137         }).map((prop) => {
138           return (
139             <div><p><strong>{prop['node-type']}:</strong></p><p>{prop['searchable-attributes']}</p></div>
140           );
141         });
142         this.setState({searchable: searchDOM, helpModalShow: true});
143       }, error => {
144         console.log(error);
145         this.setState({searchable: 'An error occurred, please try again later.', helpModalShow: true});
146       }).catch(error => {
147         console.log(error);
148         this.setState({searchable: 'An error occurred, please try again later.', helpModalShow: true});
149       });
150     };
151
152     let clearButtonClass = (value.length > 0)
153       ? 'auto-complete-clear-button'
154       : 'auto-complete-clear-button hidden';
155
156     return (
157       <div className='auto-complete-search'>
158         <AutoSuggest
159           suggestions={suggestions}
160           getSuggestionValue={suggestion => suggestion[suggestionName]}
161           onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
162           onSuggestionsClearRequested={onSuggestionsClearRequested}
163           onSuggestionSelected={(event, {suggestion}) => {
164             this.newSearchSelected(suggestion, onInvalidSearch, dispatchAnalytics, value);
165             this.props.onClearSuggestionsTextFieldRequested();
166           }}
167           renderSuggestion={this.renderSuggestion}
168           inputProps={inputProps}
169           focusFirstSuggestion={false}
170           renderSuggestionsContainer={this.renderSuggestionsContainer}/>
171         <ButtonGroup className='auto-complete-search-button-group'>
172           <Button type='submit' className={clearButtonClass}
173             onClick={onClearSuggestionsTextFieldRequested}>
174             <i className={ICON_CLASS_CLEAR} aria-hidden='true'/>
175           </Button>
176           <Button type='submit' className='auto-complete-help-button' onClick={showHelpModal}>
177             <i className={ICON_CLASS_HELP} aria-hidden='true'/>
178           </Button>
179           <Button type='submit' className='auto-complete-search-button' onClick={() => {
180             this.newSearchSelected(this.getSelectedSuggestionObj(value, cachedSuggestions),
181               onInvalidSearch, dispatchAnalytics, value);
182             this.props.onSuggestionsClearRequested();
183           }}>
184             <i className={ICON_CLASS_SEARCH} aria-hidden='true'/>
185           </Button>
186         </ButtonGroup>
187         <div className='static-modal'>
188           <Modal show={this.state.helpModalShow} onHide={closeHelpModal}>
189             <Modal.Header>
190                 <Modal.Title>Searchable Fields</Modal.Title>
191             </Modal.Header>
192             <Modal.Body>
193               <div className='modal-searchable'>
194                 {this.state.searchable}
195               </div>
196             </Modal.Body>
197             <Modal.Footer>
198                 <Button onClick={closeHelpModal}>Close</Button>
199             </Modal.Footer>
200           </Modal>
201         </div>
202       </div>
203     );
204   }
205
206   renderSuggestion(suggestion, {query}) {
207     let toHighLightArray = query.split(' ');
208     let suggestionTextArray = suggestion.text.split(' ');
209     let arrayIndex = 0;
210
211     if (suggestion.text !== NO_MATCHES_FOUND) {
212       // render the suggestion as a clickable link
213       return (
214         <div className='suggestionFlexContainer'>
215         <span key={'sugSpan1'}
216               className='suggestionColumnTwo'>
217           <Link style={{textDecoration: 'none'}}
218                 to={'/' + suggestion.route + '/' + suggestion.hashId}
219                 replace={true}>
220             {suggestionTextArray.map(
221               function () {
222                 return (
223                   <span key={arrayIndex + 'sugSpan3'}>
224                     <Highlighter key={arrayIndex + 'high'}
225                                  highlightClassName='highlight'
226                                  searchWords={toHighLightArray}
227                                  textToHighlight={suggestionTextArray[arrayIndex]}
228                                  autoEscape={true}/>
229                     { ++arrayIndex ? ' ' : ' '}
230                  </span>);
231
232               })} </Link>
233       </span>
234         </div>
235       );
236     } else {
237       // render the suggestion as plain text
238       return (
239         <div className='suggestionFlexContainer'>
240           <span key={'sugSpan1'}
241                 className='suggestionColumnTwo'>
242               {suggestionTextArray.map(
243                 function () {
244                   return (
245                     <span key={arrayIndex + 'sugSpan3'}>
246                       <Highlighter key={arrayIndex + 'high'}
247                                    highlightClassName='highlight'
248                                    searchWords={toHighLightArray}
249                                    textToHighlight={suggestionTextArray[arrayIndex]}
250                                    autoEscape={true}/>
251                       { ++arrayIndex ? ' ' : ' '}
252                    </span>);
253
254                 })}
255           </span>
256         </div>
257       );
258     }
259   }
260
261   renderSuggestionsContainer({children, ...rest}) {
262     if (children !== null && children.props.items.length < 5) {
263       rest.className = 'react-autosuggest__suggestions-containerCopy';
264     }
265     return (
266       <div {...rest.containerProps} {...rest}>
267         {children}
268       </div>
269     );
270   }
271 }