React and blur event
The problem is that a table doesn't actually have a concept of focus since it's not an input itself.
When the onBlur fires on the contained inputs we will check the relatedTarget
of the onBlur
event which should be set to the element that has RECEIVED focus (or null
). We then use a function that will traverse upwards through parentNode
s from that newly focused element and ensure that our event's currentTarget
(the table) is not an ancestor of the newly focused element. If the condition passes it is assumed that the table no longer has any focus.
const focusInCurrentTarget = ({ relatedTarget, currentTarget }) => { if (relatedTarget === null) return false; var node = relatedTarget.parentNode; while (node !== null) { if (node === currentTarget) return true; node = node.parentNode; } return false;}const onBlur = (e) => { if (!focusInCurrentTarget(e)) { console.log('table blurred'); }}const MyList = ({ items, onBlur }) => ( <table onBlur={onBlur}> <thead> <tr> <th>ID</th> <th>Title</th> <th>Publisher</th> <th>Year</th> <th>Author</th> <th>System</th> <th/> </tr> </thead> <tbody> <tr> <td>1</td> <td> <input type="text" /> </td> <td> <input type="text" /> </td> <td> <input type="text" /> </td> <td> <input type="text" /> </td> <td> <input type="text" /> </td> </tr> </tbody> </table>); ReactDOM.render( <MyList onBlur={onBlur} />, document.getElementById('root'));
table { padding: 10px; border: 1px solid red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script><div id="root"></div><br /><input type="text" />
References:
- http://www.w3schools.com/jsref/event_onblur.asp
- http://www.w3schools.com/jsref/event_currenttarget.asp
- http://www.w3schools.com/jsref/event_focus_relatedtarget.asp
- https://stackoverflow.com/a/2234986/2957138 (basis for our ancestor-checking function)
UPDATED:
Removed use of ReactDOM.findDOMNode
Without custom functions and Internet Explorer compatible, since node.contains
and document.activeElement
are supported since Internet Explorer 5, this works:
const onBlur = (e) => { if ( !e.currentTarget.contains( document.activeElement ) ) { console.log('table blurred'); }}
- The Node.contains() method returns a Boolean value indicating whether a node is a descendant of a given node or not.
- Document.activeElement returns the currently focused element, that is, the element that will get keystroke events if the user types any.
Just to hopefully synthesise David Riccitelli's helpful pointer, and wintondeshong's later insight, this worked for me:
class ActionRow extends Component { onBlur = (e) => { if ( !e.currentTarget.contains( e.relatedTarget ) ) { console.log('blur event'); } } render() { return ( <div onBlur={this.onBlur}> .. </div> ) }}
I was trying to trigger a save action based on when focus left a div full of form fields.