Search code examples
javascriptsvgprototypejs

Can Prototype extend SVG elements?


I'm trying to develop an interactive SVG map and can't seem to get Prototype to extend inline SVG elements. Here's my example code (path data removed because it's huge):

<svg id="map" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="330" height="500" viewBox="-16037,-19651 28871,44234">
    <g id="state_outline">
        <path id="state" fill="white" stroke-width="200" d="..." />
        <path id="water" fill="#a0a0ff" d="..." />
    </g>
    <g id="counties">
        <path class="county" id="adams" d="..." />
        ...
    </g>
</svg>

<div id="nottamap"></div>       

<script type="text/javascript">
    console.log($('nottamap'));
    console.log($('nottamap').identify());
    console.log($('counties'));
    console.log($('counties').identify());
</script>

The result of running that is:

<div id="nottamap">
nottamap
<g id="counties">
$("counties").identify is not a function

$() is just refusing to extend the element passed to it if it's part of the SVG element. Is there something about Prototype's interaction with XML elements that I'm not understanding, or is there a better way for me to go about this?


Solution

  • Prototype augments elements through it's Element.addMethods method. If you look at the the source code you can see this relevant part:

    var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
      Element.prototype;
    
    if (F.ElementExtensions) {
      copy(Element.Methods, elementPrototype);
      copy(Element.Methods.Simulated, elementPrototype, true);
    }
    

    Here it favours the HTMLElement.prototype which SVG elements do not inherit. It falls back to Element.prototype in those browsers (cough IE cough) that also do not support SVG. You have the choice of editing the source directly and duplicating all copy actions to SVGElement too.


    That sounds like a lot of work when you realise that a simpler hack can be used. The static Element.* methods still work when used directly. Instead of $('counties').identify() use Element.identify('counties'). Where you might do something like this:

    $$('.classname').invoke('hide');
    

    You can adopt the nice functional equivalent which is:

    $$('.classname').each(Element.hide);
    // or replace Element.hide with Effect.fade or any other effect
    

    The downside is you lose the ability to use method chaining.