0

I know that it is possible to attach plain-old Javascript event handlers to elements within an SVG tag. I am looking to duplicate very many instances of an object, so I am planning to use the <def> and <use> tags to simplify things a bit. However each of my children items will need to handle "click" and other events. I could define those events for each child, but it would be nice if I could somehow define them ONCE in the initial items, and then just "reuse" that click event. Is this possible?

https://jsfiddle.net/khyp1o0w/

<svg width="400" height="400" viewBox="0 0 400 400">
    <defs>
        <rect id="someDef" x="0" y="0" width="60" height="30" fill="#f00" />  
        <!--  ^^ would like to define an event here... -->
    </defs>

    <use x="150" y="150" xlink:href="#someDef" />
    <use x="250" y="250" xlink:href="#someDef" />  
    <!--  ^^  ... that is used by these guys  -->

</svg>

EDIT: Adding example SVG.

loneboat
  • 2,504
  • 4
  • 24
  • 36
  • That's what we call [event delegation](http://stackoverflow.com/q/1687296/2813224) but instead of registering event on children, you register on parent. – zer00ne Jan 13 '17 at 23:11
  • @zer00ne: I'm aware of event delegation. I'm trying to use this in an SVG element on a tag, and have it apply to the tags which reference it. Regular event delegation does not seem to apply here, since the tags are not truly children of the tag which they reference. – loneboat Jan 13 '17 at 23:14
  • Like I have no idea what you have so provide a [mcve] and perhaps we can see if event delegation is possible. – zer00ne Jan 13 '17 at 23:53
  • @zer00ne: I went ahead and added a minimal example. I really didn't think one was necessary, since I'm just asking a straightforward question about tags... :-\ – loneboat Jan 14 '17 at 02:05

1 Answers1

1

IIRC elements targeted by a use element should be internally copied into the <use> element just like if the <use> were an iframe, and the content were cloned.

So this means that events attached on the original node won't be copied onto the copies, unless this event is part of the node markup (inlined). But chrome don't really follow specs here and won't even make it work... So here is an example for FF :

// this won't work
document.getElementById('someDef').addEventListener('click', function() {
  this.setAttribute('stroke', randomColor());
});
<svg width="400" height="400" viewBox="0 0 400 400">
  <defs>
    <script type="application/javascript">
      function randomColor() {
        var letters = '0123456789ABCDEF'.split('');
        var color = '#';
        for (var i = 0; i < 6; i++) {
          color += letters[~~(Math.random() * 15)];
        }
        return color;
      }
    </script>
    <!-- this will work in FF -->
    <rect id="someDef" onclick="this.setAttribute('fill',randomColor())" x="0" y="0" width="60" height="30" fill="#f00" />
  </defs>

  <use x="150" y="150" xlink:href="#someDef" />
  <use x="250" y="250" xlink:href="#someDef" />
</svg>

You could of course use event delegation by listening for click events on your root svg element, but this would work only for direct target elements, not for nested ones :

var svg = document.querySelector('svg');
svg.addEventListener('click', function(e) {
  var usedTargetID = e.target.getAttributeNS("http://www.w3.org/1999/xlink", 'href');
  switch (usedTargetID) {
    case '#direct':
      clickDirect(e);
      break;
    case '#nested':
      clickNested(e);
      break;
    default:
      return;
  }
});

function clickDirect(e) {
  // what you seem to want
  e.target.setAttribute('fill', randomColor());
}

function clickNested(e) {
  // will set both nested...
  e.target.setAttribute('fill', randomColor());
}

function randomColor() {
  var letters = '0123456789ABCDEF'.split('');
  var color = '#';
  for (var i = 0; i < 6; i++) {
    color += letters[~~(Math.random() * 15)];
  }
  return color;
}
<svg width="400" height="400" viewBox="0 0 400 400">
  <defs>
    <rect id="direct" x="0" y="0" width="60" height="30" />
    <g id="nested">
      <!-- you can only access this -->
      <!-- not its content individually -->
      <rect x="80" y="0" width="60" height="30" />
      <rect x="20" y="70" width="60" height="30" />
    </g>

  </defs>

  <use x="150" y="50" xlink:href="#direct" />
  <use x="250" y="150" xlink:href="#nested" />
</svg>
Or you could also add an event per element, but this would have the same limitations as delegation...
Kaiido
  • 87,051
  • 7
  • 143
  • 194