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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With