Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 938
Phantom Watson Avatar asked Feb 03 '12 23:02

Phantom Watson


2 Answers

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.

like image 142
clockworkgeek Avatar answered Oct 28 '22 20:10

clockworkgeek


clockworkgeek's suggestion to change the prototype source code worked very well for me. In prototype v. 1.7, all it takes is to change the relevant part to

var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
  Element.prototype;

if (F.ElementExtensions) {
  if (window.SVGElement) {
    copy(Element.Methods, SVGElement.prototype);
    copy(Element.Methods.Simulated, SVGElement.prototype, true);
  }
  copy(Element.Methods, elementPrototype);
  copy(Element.Methods.Simulated, elementPrototype, true);
}

In prototype v. 1.7.1, the needed changes are:

var ELEMENT_PROTOTYPE = window.HTMLElement ? HTMLElement.prototype :
  Element.prototype;

if (F.ElementExtensions) {
  if (window.SVGElement) {
    mergeMethods(SVGElement.prototype, Element.Methods);
    mergeMethods(SVGElement.prototype, Element.Methods.Simulated, true);
  }
  mergeMethods(ELEMENT_PROTOTYPE, Element.Methods);
  mergeMethods(ELEMENT_PROTOTYPE, Element.Methods.Simulated, true);
}

I found the ready-made solution in this blog post

like image 32
Thomas Zumbrunn Avatar answered Oct 28 '22 20:10

Thomas Zumbrunn