1 | import React from 'react';
|
2 | var ReactDom = require('react-dom');
|
3 |
|
4 | export class Select extends React.Component {
|
5 | constructor(props) {
|
6 | super(props);
|
7 | this.getVisibleItems = this.getVisibleItems.bind(this);
|
8 | this.handleOutsideClick = this.handleOutsideClick.bind(this);
|
9 | this.inputOnChange = this.inputOnChange.bind(this);
|
10 | this.inputOnKeyPress = this.inputOnKeyPress.bind(this);
|
11 | this.inputOnKeyDown = this.inputOnKeyDown.bind(this);
|
12 | this.linkOnKeyDown = this.linkOnKeyDown.bind(this);
|
13 | this.setNextHighlightedItem = this.setNextHighlightedItem.bind(this);
|
14 | this.findIndex = this.findIndex.bind(this);
|
15 | }
|
16 |
|
17 | componentWillMount() {
|
18 | this.setState({
|
19 | open: false,
|
20 | items: this.props.items ? this.props.items : {},
|
21 | filter: '',
|
22 | selectedItem: '',
|
23 | selectedItemLabel: '',
|
24 | visibleItems: [],
|
25 | tabIndex: this.props.tabIndex ? this.props.tabIndex : null,
|
26 | currentlyHighlighted: ''
|
27 | });
|
28 | document.addEventListener('click', this.handleOutsideClick, false);
|
29 | }
|
30 |
|
31 | componentDidMount() {
|
32 | this.getVisibleItems();
|
33 | document.addEventListener('keydown', (e)=> {
|
34 | if (e.key === "Escape") {
|
35 | this.setState({
|
36 | open: false,
|
37 | filter: ''
|
38 | });
|
39 | this.setState({
|
40 | currentlyHighlighted: '',
|
41 | });
|
42 | this.getVisibleItems();
|
43 | if (ReactDom.findDOMNode(this).contains(e.target)) {
|
44 | this.link.focus();
|
45 | }
|
46 | }
|
47 | });
|
48 | };
|
49 |
|
50 | componentWillUnmount() {
|
51 | document.removeEventListener('click', this.handleOutsideClick, false);
|
52 | };
|
53 |
|
54 | toggle(value) {
|
55 | this.setState({
|
56 | open: value
|
57 | });
|
58 | if (value == false) {
|
59 | this.setState({
|
60 | filter: ''
|
61 | });
|
62 | } else {
|
63 | if(this.state.selectedItem){
|
64 | this.setState({
|
65 | currentlyHighlighted: this.state.selectedItem
|
66 | });
|
67 | }
|
68 | }
|
69 | };
|
70 |
|
71 | componentWillReceiveProps(nextProps) {
|
72 | if (nextProps.items !== this.state.items) {
|
73 | this.setState({items: nextProps.items}, () => {
|
74 | this.getVisibleItems();
|
75 | });
|
76 | }
|
77 | }
|
78 |
|
79 | submit(value) {
|
80 | this.props.onChange(value);
|
81 | this.setState({
|
82 | selectedItem: value,
|
83 | selectedItemLabel: this.props.items[value],
|
84 | open: false,
|
85 | currentlyHighlighted: ''
|
86 | }, ()=> {
|
87 | this.getVisibleItems();
|
88 | })
|
89 | }
|
90 |
|
91 | getVisibleItems(isSearching) {
|
92 | var first = true;
|
93 | const visibleItems = [];
|
94 | Object.keys(this.props.items).forEach((key)=> {
|
95 | if (
|
96 | !this.state.filter
|
97 | ||
|
98 | this.props.items[key].toLowerCase().indexOf(this.state.filter.toLowerCase().trim())
|
99 | !== -1
|
100 | ) {
|
101 | var className = '';
|
102 | if (isSearching) {
|
103 | if (first == true) {
|
104 | first = false;
|
105 | className = 'item item-selected';
|
106 | this.setState({
|
107 | currentlyHighlighted: key
|
108 | })
|
109 | } else {
|
110 | className = 'item'
|
111 | }
|
112 | } else {
|
113 | className = (
|
114 | (key == this.state.currentlyHighlighted && this.state.selectedItem == '')
|
115 | ||
|
116 | (key == this.state.selectedItem && this.state.currentlyHighlighted == '')
|
117 | ||
|
118 | (
|
119 | this.state.currentlyHighlighted != ''
|
120 | &&
|
121 | this.state.selectedItem != ''
|
122 | &&
|
123 | key == this.state.currentlyHighlighted
|
124 | )
|
125 | )
|
126 | ? 'item item-selected' : 'item';
|
127 | }
|
128 | visibleItems.push(
|
129 | <div
|
130 | onClick={() => {
|
131 | this.submit(key);
|
132 | }}
|
133 | key={key}
|
134 | className={className}
|
135 | >{this.props.items[key]}
|
136 | </div>
|
137 | );
|
138 |
|
139 | }
|
140 | });
|
141 |
|
142 | if (visibleItems.length === 0) {
|
143 | visibleItems.push(
|
144 | <div
|
145 | key={null}
|
146 | className="item item-no-results"
|
147 | >No results found</div>
|
148 | );
|
149 | this.setState({
|
150 | currentlyHighlighted: ''
|
151 | })
|
152 | }
|
153 |
|
154 | this.setState({
|
155 | visibleItems: visibleItems
|
156 | })
|
157 | };
|
158 |
|
159 | handleOutsideClick(e) {
|
160 | if (!ReactDom.findDOMNode(this).contains(e.target)) {
|
161 | this.setState({
|
162 | open: false,
|
163 | filter: ''
|
164 | }, ()=> {
|
165 | this.getVisibleItems();
|
166 | })
|
167 | }
|
168 | };
|
169 |
|
170 | findIndex(item) {
|
171 | return item.key == this.state.currentlyHighlighted;
|
172 | }
|
173 |
|
174 | setNextHighlightedItem(direction = null, isSearching = false) {
|
175 | const currentIndex = this.state.visibleItems.findIndex(this.findIndex);
|
176 | let newIndex = 0;
|
177 | if (direction == 'down' && currentIndex < this.state.visibleItems.length - 1 && currentIndex != -1) {
|
178 | newIndex = currentIndex + 1;
|
179 | } else if (direction == 'up' && currentIndex > 0) {
|
180 | newIndex = currentIndex - 1;
|
181 | } else if (!direction){
|
182 | newIndex = 0;
|
183 | }
|
184 | this.setState({
|
185 | currentlyHighlighted: this.state.visibleItems[newIndex].key
|
186 | }, ()=> {
|
187 | this.getVisibleItems(isSearching);
|
188 | });
|
189 | };
|
190 |
|
191 | inputOnKeyDown(e) {
|
192 | if (e.key === 'ArrowDown') {
|
193 | this.setNextHighlightedItem('down');
|
194 | }
|
195 |
|
196 | if (e.key === 'ArrowUp') {
|
197 | this.setNextHighlightedItem('up');
|
198 | }
|
199 | }
|
200 |
|
201 | inputOnKeyPress(e) {
|
202 | if (e.key === 'Esc') {
|
203 | this.toggle(!this.state.open);
|
204 |
|
205 | }
|
206 | if (e.key === 'Enter') {
|
207 | e.preventDefault();
|
208 | if (this.state.currentlyHighlighted != '') {
|
209 | this.submit(this.state.currentlyHighlighted);
|
210 | this.toggle(!this.state.open);
|
211 | this.link.focus();
|
212 | }
|
213 | return false;
|
214 | }
|
215 | }
|
216 |
|
217 | inputOnChange(e) {
|
218 | this.setState({
|
219 | filter: e.target.value
|
220 | }, ()=> {
|
221 | this.setNextHighlightedItem(null, true);
|
222 | });
|
223 | }
|
224 |
|
225 | linkOnKeyDown(e) {
|
226 | if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
227 | this.setState({
|
228 | open: true
|
229 | })
|
230 | }
|
231 | }
|
232 |
|
233 | render() {
|
234 | return (
|
235 |
|
236 | <div className="select-react-redux-container">
|
237 | <a href="#"
|
238 | tabIndex={this.state.tabIndex}
|
239 | onClick={()=> {
|
240 | this.toggle(!this.state.open)
|
241 | }}
|
242 | onKeyPress={()=> {
|
243 | this.setState({open: true})
|
244 | }}
|
245 | ref={(e) => {
|
246 | this.link = e;
|
247 | }}
|
248 | onKeyDown={this.linkOnKeyDown}
|
249 | className={this.state.open ? 'selected selected-open' : 'selected'}
|
250 | >
|
251 | <div
|
252 | className={Object.keys(this.state.items).length == 0 ? 'top-bar top-bar-empty' : 'top-bar'}>
|
253 | {this.state.selectedItemLabel
|
254 | ? this.state.selectedItemLabel
|
255 | : Object.keys(this.state.items).length == 0 ? 'No options available' : 'Please select...'}
|
256 | </div>
|
257 | </a>
|
258 |
|
259 | <div className={this.state.open ? 'results-container open' : 'results-container' }>
|
260 |
|
261 | <div className="input-container">
|
262 | <input
|
263 | type="text"
|
264 | autoCorrect="off"
|
265 | autoCapitalize="off"
|
266 | spellCheck="false"
|
267 | autoComplete="off"
|
268 | ref={search => search && search.focus()}
|
269 | value={this.state.filter}
|
270 | onKeyPress={this.inputOnKeyPress}
|
271 | onChange={this.inputOnChange}
|
272 | onKeyDown={this.inputOnKeyDown}
|
273 | />
|
274 | </div>
|
275 | {this.state.visibleItems}
|
276 | </div>
|
277 | </div>
|
278 | );
|
279 | }
|
280 | }
|