5
5
USE:
6
6
7
7
<NCAutoSuggest
8
- statekey ={key}
8
+ parentKey ={key}
9
9
value={value}
10
10
onChange={this.handleInputUpdate}
11
11
onSelect={this.handleSelection}
14
14
PROPS
15
15
16
16
onChange(key, value) -- returns `key` and `value` for the input field
17
- onSelect(key, valuem, id) -- returns `key` and `value` for the final submission
18
- as well as the matching id
17
+
18
+ onSelect(parentKey, value, id)
19
+ -- returns `state` and `value` for the final submission as well as the
20
+ matching id. `value` is then passed back to NCAutoSuggest as the
21
+ search field input value.
19
22
20
23
This will look up matching nodes via FIND_MATCHING_NODES nc-logic request.
21
24
22
25
This is a simple HTML component that will allow users to enter arbitrary
23
26
text input. Any partial node labels will display as a list of popup
24
27
menu options.
25
28
26
- It can be used in a NCNode or NCEdge
29
+ It can be used in a NCSearch or NCEdge
30
+ (NOTE NCNode does not not use NCAutoSuggest, but displays a matchlist using
31
+ a mechanism similar to NCAutoSuggest -- the key difference is that NCNode's
32
+ matchlist is simply a static display list to let you know which nodes match
33
+ the current input field, and does NOT support selecting a match.)
27
34
28
- `statekey` provides a unique key for source/target selection
35
+ `parentKey` provides a unique key to determine whether this NCAutoSuggest
36
+ component is being used for a `search`, a `source`, or a `target` selection
29
37
30
- Replaces the AutoComplete and AutoSuggest components.
38
+ Replaces the deprecated AutoComplete and AutoSuggest components.
31
39
32
40
\*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/
33
41
@@ -51,12 +59,19 @@ class NCAutoSuggest extends UNISYS.Component {
51
59
this . state = {
52
60
matches : [ ] , // {id, label}
53
61
higlightedLine : - 1 ,
54
- isValidNode : true
62
+ isValidNode : true ,
63
+ uShowMatchlist : false
55
64
} ;
56
65
66
+ this . m_UIInputFocus = this . m_UIInputFocus . bind ( this ) ;
67
+ this . m_UIInputClick = this . m_UIInputClick . bind ( this ) ;
57
68
this . m_UIUpdate = this . m_UIUpdate . bind ( this ) ;
58
- this . m_UISelect = this . m_UISelect . bind ( this ) ;
69
+ this . m_UISelectByLabel = this . m_UISelectByLabel . bind ( this ) ;
70
+ this . m_UISelectById = this . m_UISelectById . bind ( this ) ;
59
71
this . m_UIKeyDown = this . m_UIKeyDown . bind ( this ) ;
72
+ this . m_UIMouseHighlightLine = this . m_UIMouseHighlightLine . bind ( this ) ;
73
+ this . m_UIMouseUnhighlightLine = this . m_UIMouseUnhighlightLine . bind ( this ) ;
74
+ this . m_UIHighlightLine = this . m_UIHighlightLine . bind ( this ) ;
60
75
this . m_UIClickOutside = this . m_UIClickOutside . bind ( this ) ;
61
76
62
77
document . addEventListener ( 'click' , this . m_UIClickOutside ) ;
@@ -70,6 +85,27 @@ class NCAutoSuggest extends UNISYS.Component {
70
85
}
71
86
72
87
/**
88
+ * User has clicked inside the input field to set selection point
89
+ * This is needed to restore the selection point after a blur
90
+ * `focus` fires before `click`
91
+ */
92
+ m_UIInputFocus ( event ) {
93
+ event . target . select ( ) ;
94
+ this . m_UIUpdate ( event ) ;
95
+ }
96
+
97
+ /**
98
+ * User has clicked in the input field, so update and show the matchlist
99
+ * and catch the event to prevent the document click handler from firing other actions
100
+ */
101
+ m_UIInputClick ( event ) {
102
+ event . preventDefault ( ) ; // this prevents the document click handler from closing the matchlist
103
+ event . stopPropagation ( ) ;
104
+ this . setState ( { uShowMatchlist : true } ) ;
105
+ }
106
+
107
+ /**
108
+ * User has typed in the input field, or the field is getting focus again.
73
109
* This processes the form data before passing it on to the parent handler.
74
110
* The callback function is generally an input state update method in
75
111
* NCNode or NCEdge
@@ -79,7 +115,6 @@ class NCAutoSuggest extends UNISYS.Component {
79
115
const { onChange } = this . props ;
80
116
const key = event . target . id ;
81
117
const value = event . target . value ;
82
-
83
118
// save the selection cursor position
84
119
const selstart = event . target . selectionStart ;
85
120
const inputEl = event . target ;
@@ -93,7 +128,7 @@ class NCAutoSuggest extends UNISYS.Component {
93
128
return { id : d . id , label : d . label } ;
94
129
} )
95
130
: undefined ;
96
- this . setState ( { matches, isValidNode } ) ;
131
+ this . setState ( { matches, isValidNode, uShowMatchlist : true } ) ;
97
132
if ( typeof onChange === 'function' )
98
133
onChange ( key , value , ( ) => {
99
134
// restore selection cursor position
@@ -103,41 +138,78 @@ class NCAutoSuggest extends UNISYS.Component {
103
138
} ) ;
104
139
}
105
140
/**
106
- * User has clicked an item in the matchlist, selecting one of the autosuggest items
141
+ * User has clicked an item in the matchlist,
142
+ * or selected an item by typing ENTER
143
+ * selecting one of the autosuggest items
107
144
* @param {Object } event
108
- * @param {string } key Usually either `source` or `target`
109
- * @param {string } value
145
+ * @param {string } parentKey Either `search`, `source` or `target`
146
+ * @param {string } value The autosuggest input value
110
147
*/
111
- m_UISelect ( event , key , value ) {
148
+ m_UISelectByLabel ( event , parentKey , value ) {
112
149
event . preventDefault ( ) ; // catch click to close matchlist
113
150
event . stopPropagation ( ) ;
114
151
const { onSelect } = this . props ;
115
152
const { matches } = this . state ;
116
- const matchedNode = matches ? matches . find ( n => n . label === value ) : undefined ;
117
- this . setState ( { isValidNode : matchedNode , matches : [ ] , higlightedLine : - 1 } ) ; // clear matches
118
- if ( typeof onSelect === 'function' )
119
- onSelect ( key , value , matchedNode ? matchedNode . id : undefined ) ; // callback function NCEdge.uiSourceTargetInputUpdate
153
+ const matchedNodeViaID = matches ? matches . find ( n => n . id === value ) : undefined ;
154
+ this . setState ( {
155
+ isValidNode : matchedNodeViaID ,
156
+ matches : [ ] ,
157
+ higlightedLine : - 1 ,
158
+ uShowMatchlist : false
159
+ } ) ; // clear matches
160
+ if ( typeof onSelect === 'function' ) {
161
+ onSelect (
162
+ parentKey ,
163
+ value ,
164
+ matchedNodeViaID ? matchedNodeViaID . id : undefined // ...or id, not both
165
+ ) ; // callback function NCEdge.uiSourceTargetInputUpdate
166
+ }
167
+ }
168
+
169
+ m_UISelectById ( event , parentKey , id ) {
170
+ event . preventDefault ( ) ; // catch click to close matchlist
171
+ event . stopPropagation ( ) ;
172
+ const { onSelect, value } = this . props ;
173
+ const { matches } = this . state ;
174
+ const matchedNodeViaID = matches ? matches . find ( n => n . id === id ) : undefined ;
175
+ this . setState ( {
176
+ isValidNode : matchedNodeViaID ,
177
+ matches : [ ] ,
178
+ higlightedLine : - 1 ,
179
+ uShowMatchlist : false
180
+ } ) ; // clear matches
181
+ if ( typeof onSelect === 'function' ) {
182
+ onSelect (
183
+ parentKey ,
184
+ value , // show the current input field value
185
+ matchedNodeViaID ? matchedNodeViaID . id : undefined // ...or `id`, not both
186
+ ) ; // callback function NCEdge.uiSourceTargetInputUpdate
187
+ }
120
188
}
189
+
121
190
/**
122
191
* Handle key strokes
123
- * -- Hitting up/down arrow will select the higlight
124
- * -- Hitting Esc will cancel the autosuggest, also hitting Tab will prevent selecting the next field
125
- * -- Hitting Enter will select the item
192
+ * -- Typing UP/DOWN arrow will select the higlight
193
+ * -- Typing ESC will cancel the autosuggest, also hitting Tab will prevent selecting the next field
194
+ * -- Typing ENTER will select the item
126
195
* @param {Object } event
127
196
*/
128
197
m_UIKeyDown ( event ) {
129
198
const { matches, higlightedLine } = this . state ;
130
- const { statekey , value, onSelect } = this . props ;
199
+ const { parentKey , value, onSelect } = this . props ;
131
200
const keystroke = event . key ;
132
201
const lastLine = matches ? matches . length : - 1 ;
133
202
let newHighlightedLine = higlightedLine ;
134
203
if ( keystroke === 'Enter' ) {
135
- let selectedValue = value ;
136
- if ( higlightedLine > - 1 ) {
137
- // there is highlight, so select that
138
- selectedValue = matches [ higlightedLine ] . label ;
204
+ if ( higlightedLine > - 1 && matches ) {
205
+ // make sure matches exists, b/c hitting Enter with a typo can end up with bad match
206
+ // there is highlight, so select that using the id in the matchlist
207
+ const id = matches [ higlightedLine ] . id ;
208
+ this . m_UISelectById ( event , parentKey , id ) ; // user selects current highlight
209
+ } else if ( value !== '' ) {
210
+ // Create a new node -- see also NCSearch
211
+ this . m_UISelectByLabel ( event , parentKey , value ) ; // user selects current highlight
139
212
}
140
- this . m_UISelect ( event , statekey , selectedValue ) ; // user selects current highlight
141
213
}
142
214
if ( keystroke === 'Escape' || keystroke === 'Tab' ) {
143
215
event . preventDefault ( ) ; // prevent tab key from going to the next field
@@ -151,52 +223,75 @@ class NCAutoSuggest extends UNISYS.Component {
151
223
newHighlightedLine > - 1 &&
152
224
lastLine > 0
153
225
) {
154
- newHighlightedLine = Math . min ( lastLine - 1 , Math . max ( 0 , newHighlightedLine ) ) ;
155
- this . setState ( { higlightedLine : newHighlightedLine } ) ;
156
- const highlightedNode = matches [ newHighlightedLine ] ;
157
- UDATA . LocalCall ( 'AUTOSUGGEST_HILITE_NODE' , { nodeId : highlightedNode . id } ) ;
226
+ this . m_UIHighlightLine ( newHighlightedLine ) ;
158
227
}
159
228
}
160
229
230
+ m_UIMouseHighlightLine ( event , line ) {
231
+ this . m_UIHighlightLine ( line ) ;
232
+ }
233
+ m_UIMouseUnhighlightLine ( event ) {
234
+ // Placeholder for future functionality
235
+ // Catch the event, but don't do anything.
236
+ // We want to keep the matchlist open even if you move the mouse
237
+ // outside of the line.
238
+ }
239
+
240
+ m_UIHighlightLine ( line ) {
241
+ const { matches } = this . state ;
242
+ const lastLine = matches ? matches . length : - 1 ;
243
+ line = Math . min ( lastLine - 1 , Math . max ( 0 , line ) ) ;
244
+ this . setState ( { higlightedLine : line , uShowMatchlist : true } ) ;
245
+ const highlightedNode = matches [ line ] ;
246
+ UDATA . LocalCall ( 'AUTOSUGGEST_HILITE_NODE' , { nodeId : highlightedNode . id } ) ;
247
+ }
248
+
249
+ // Clicking outside of the matchlist should close the autosuggest
161
250
m_UIClickOutside ( event ) {
162
- if ( ! event . defaultPrevented ) {
163
- this . setState ( { matches : [ ] , higlightedLine : - 1 } ) ; // close autosuggest
164
- }
251
+ if ( event . defaultPrevented )
252
+ return ; // clicking on the input field or the matchlist catches the click and prevents inadvertently closing the matchlist
253
+ else this . setState ( { matches : [ ] , higlightedLine : - 1 , uShowMatchlist : false } ) ; // close matchlist
165
254
}
166
255
167
256
render ( ) {
168
- const { matches, higlightedLine, isValidNode } = this . state ;
169
- const { statekey , value, onSelect } = this . props ;
257
+ const { matches, higlightedLine, isValidNode, uShowMatchlist } = this . state ;
258
+ const { parentKey , value, onSelect } = this . props ;
170
259
const matchList =
171
260
matches && matches . length > 0
172
261
? matches . map ( ( n , i ) => (
173
262
< div
174
263
key = { `${ n . label } ${ i } ` }
175
264
value = { n . label }
176
265
className = { higlightedLine === i ? 'highlighted' : '' }
177
- onClick = { event => this . m_UISelect ( event , statekey , n . label ) }
266
+ onClick = { event => this . m_UISelectById ( event , parentKey , n . id ) }
267
+ onMouseEnter = { event => this . m_UIMouseHighlightLine ( event , i ) }
178
268
>
179
- { n . label }
269
+ { n . label } < span className = "id" > # { n . id } </ span >
180
270
</ div >
181
271
) )
182
272
: undefined ;
183
273
return (
184
274
< div style = { { position : 'relative' , flexGrow : '1' } } >
185
275
< div className = "helptop" > Click on a node, or type a node name</ div >
186
276
< input
187
- id = { statekey }
188
- key = { `${ statekey } input` }
277
+ id = { parentKey }
278
+ key = { `${ parentKey } input` }
189
279
value = { value }
190
280
type = "string"
191
281
className = { ! isValidNode ? 'invalid' : '' }
192
282
onChange = { this . m_UIUpdate }
193
283
onKeyDown = { this . m_UIKeyDown }
194
- onFocus = { e => e . target . select ( ) }
284
+ onFocus = { this . m_UIInputFocus }
285
+ onClick = { this . m_UIInputClick }
195
286
autoComplete = "off" // turn off Chrome's default autocomplete, which conflicts
196
287
/>
197
288
< br />
198
- { matchList && (
199
- < div id = "matchlist" className = "matchlist" >
289
+ { uShowMatchlist && matchList && (
290
+ < div
291
+ id = "matchlist"
292
+ className = "matchlist"
293
+ onMouseLeave = { this . m_UIMouseUnhighlightLine }
294
+ >
200
295
{ matchList }
201
296
</ div >
202
297
) }
0 commit comments