I use this D3 snippet to move SVG g elements to top of the rest element as SVG render order depends on the order of elements in DOM, and there is no z index:
One solution could be to use event delegation. This fairly simple paradigm is commonplace in jQuery (which gave me the idea to try it here.)
By extending the d3.selection prototype with a delegated event listener we can listen for events on a parent element but only apply the handler if the event's target is also our desired target.
So instead of:
d3.select('#targetElement').on('mouseout',function(){})
You would use:
d3.select('#targetElementParent').delegate('mouseout','#targetElement',function(){})
Now it doesn't matter if the events are lost when you move elements or even if you add/edit/delete elements after creating the listeners.
Here's the demo. Tested on Chrome 37, IE 11 and Firefox 31. I welcome constructive feedback but please note that I am not at all familiar with d3.js so could easily have missed something fundamental ;)
//prototype. delegated events
d3.selection.prototype.delegate = function(event, targetid, handler) {
return this.on(event, function() {
var eventTarget = d3.event.target.parentNode,
target = d3.select(targetid)[0][0];
if (eventTarget === target) {//only perform event handler if the eventTarget and intendedTarget match
handler.call(eventTarget, eventTarget.__data__);
}
});
};
//add event listeners insead of .on()
d3.select('#svg').delegate('mouseover','#g2',function(){
console.log('mouseover #g2');
}).delegate('mouseout','#g2',function(){
console.log('mouseout #g2');
})
//initial move to front to test that the event still works
d3.select('#g2').moveToFront();
http://jsfiddle.net/f8bfw4y8/
Following Makyen's useful feedback I have made a few improvements to allow the delegated listener to be applied to ALL matched children. EG "listen for mouseover on each g within svg"
Here's the fiddle. Snippet below.
//prototype. move to front
d3.selection.prototype.moveToFront = function () {
return this.each(function () {
this.parentNode.appendChild(this);
});
};
//prototype. delegated events
d3.selection.prototype.delegate = function(event, targetselector, handler) {
var self = this;
return this.on(event, function() {
var eventTarget = d3.event.target,
target = self.selectAll(targetselector);
target.each(function(){
//only perform event handler if the eventTarget and intendedTarget match
if (eventTarget === this) {
handler.call(eventTarget, eventTarget.__data__);
} else if (eventTarget.parentNode === this) {
handler.call(eventTarget.parentNode, eventTarget.parentNode.__data__);
}
});
});
};
var testmessage = document.getElementById("testmessage");
//add event listeners insead of .on()
//EG: onmouseover/out of ANY within #svg:
d3.select('#svg').delegate('mouseover','g',function(){
console.log('mouseover',this);
testmessage.innerHTML = "mouseover #"+this.id;
}).delegate('mouseout','g',function(){
console.log('mouseout',this);
testmessage.innerHTML = "mouseout #"+this.id;
});
/* Note: Adding another .delegate listener REPLACES any existing listeners of this event on this node. Uncomment this to see.
//EG2 onmouseover of just the #g3
d3.select('#svg').delegate('mouseover','#g3',function(){
console.log('mouseover of just #g3',this);
testmessage.innerHTML = "mouseover #"+this.id;
});
//to resolve this just delegate the listening to another parent node eg:
//d3.select('body').delegate('mouseover','#g3',function(){...
*/
//initial move to front for testing. OP states that the listener is lost after the element is moved in the DOM.
d3.select('#g2').moveToFront();
svg {height:300px; width:300px;}
rect {fill: pink;}
#g2 rect {fill: green;}
#testmessage {position:absolute; top:50px; right:50px;}
As with all delegated listeners if you move the target element outside of the parent you have delegated the listening to then naturally the events for that child are lost. However, there is nothing to stop you delegating the event listening to the body tag as you'll never move a child outside of that. EG:
d3.select('body').delegate('mouseover','g',function(){...