55 USE:
66
77 <NCAutoSuggest
8- statekey ={key}
8+ parentKey ={key}
99 value={value}
1010 onChange={this.handleInputUpdate}
1111 onSelect={this.handleSelection}
1414 PROPS
1515
1616 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.
1922
2023 This will look up matching nodes via FIND_MATCHING_NODES nc-logic request.
2124
2225 This is a simple HTML component that will allow users to enter arbitrary
2326 text input. Any partial node labels will display as a list of popup
2427 menu options.
2528
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.)
2734
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
2937
30- Replaces the AutoComplete and AutoSuggest components.
38+ Replaces the deprecated AutoComplete and AutoSuggest components.
3139
3240\*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/
3341
@@ -51,12 +59,19 @@ class NCAutoSuggest extends UNISYS.Component {
5159 this . state = {
5260 matches : [ ] , // {id, label}
5361 higlightedLine : - 1 ,
54- isValidNode : true
62+ isValidNode : true ,
63+ uShowMatchlist : false
5564 } ;
5665
66+ this . m_UIInputFocus = this . m_UIInputFocus . bind ( this ) ;
67+ this . m_UIInputClick = this . m_UIInputClick . bind ( this ) ;
5768 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 ) ;
5971 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 ) ;
6075 this . m_UIClickOutside = this . m_UIClickOutside . bind ( this ) ;
6176
6277 document . addEventListener ( 'click' , this . m_UIClickOutside ) ;
@@ -70,6 +85,27 @@ class NCAutoSuggest extends UNISYS.Component {
7085 }
7186
7287 /**
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.
73109 * This processes the form data before passing it on to the parent handler.
74110 * The callback function is generally an input state update method in
75111 * NCNode or NCEdge
@@ -79,7 +115,6 @@ class NCAutoSuggest extends UNISYS.Component {
79115 const { onChange } = this . props ;
80116 const key = event . target . id ;
81117 const value = event . target . value ;
82-
83118 // save the selection cursor position
84119 const selstart = event . target . selectionStart ;
85120 const inputEl = event . target ;
@@ -93,7 +128,7 @@ class NCAutoSuggest extends UNISYS.Component {
93128 return { id : d . id , label : d . label } ;
94129 } )
95130 : undefined ;
96- this . setState ( { matches, isValidNode } ) ;
131+ this . setState ( { matches, isValidNode, uShowMatchlist : true } ) ;
97132 if ( typeof onChange === 'function' )
98133 onChange ( key , value , ( ) => {
99134 // restore selection cursor position
@@ -103,41 +138,78 @@ class NCAutoSuggest extends UNISYS.Component {
103138 } ) ;
104139 }
105140 /**
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
107144 * @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
110147 */
111- m_UISelect ( event , key , value ) {
148+ m_UISelectByLabel ( event , parentKey , value ) {
112149 event . preventDefault ( ) ; // catch click to close matchlist
113150 event . stopPropagation ( ) ;
114151 const { onSelect } = this . props ;
115152 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+ }
120188 }
189+
121190 /**
122191 * 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
126195 * @param {Object } event
127196 */
128197 m_UIKeyDown ( event ) {
129198 const { matches, higlightedLine } = this . state ;
130- const { statekey , value, onSelect } = this . props ;
199+ const { parentKey , value, onSelect } = this . props ;
131200 const keystroke = event . key ;
132201 const lastLine = matches ? matches . length : - 1 ;
133202 let newHighlightedLine = higlightedLine ;
134203 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
139212 }
140- this . m_UISelect ( event , statekey , selectedValue ) ; // user selects current highlight
141213 }
142214 if ( keystroke === 'Escape' || keystroke === 'Tab' ) {
143215 event . preventDefault ( ) ; // prevent tab key from going to the next field
@@ -151,52 +223,75 @@ class NCAutoSuggest extends UNISYS.Component {
151223 newHighlightedLine > - 1 &&
152224 lastLine > 0
153225 ) {
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 ) ;
158227 }
159228 }
160229
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
161250 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
165254 }
166255
167256 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 ;
170259 const matchList =
171260 matches && matches . length > 0
172261 ? matches . map ( ( n , i ) => (
173262 < div
174263 key = { `${ n . label } ${ i } ` }
175264 value = { n . label }
176265 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 ) }
178268 >
179- { n . label }
269+ { n . label } < span className = "id" > # { n . id } </ span >
180270 </ div >
181271 ) )
182272 : undefined ;
183273 return (
184274 < div style = { { position : 'relative' , flexGrow : '1' } } >
185275 < div className = "helptop" > Click on a node, or type a node name</ div >
186276 < input
187- id = { statekey }
188- key = { `${ statekey } input` }
277+ id = { parentKey }
278+ key = { `${ parentKey } input` }
189279 value = { value }
190280 type = "string"
191281 className = { ! isValidNode ? 'invalid' : '' }
192282 onChange = { this . m_UIUpdate }
193283 onKeyDown = { this . m_UIKeyDown }
194- onFocus = { e => e . target . select ( ) }
284+ onFocus = { this . m_UIInputFocus }
285+ onClick = { this . m_UIInputClick }
195286 autoComplete = "off" // turn off Chrome's default autocomplete, which conflicts
196287 />
197288 < 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+ >
200295 { matchList }
201296 </ div >
202297 ) }
0 commit comments