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
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.