Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inline SVG and HTML DOM Scope for .getElementById

The problem space:

I have an SVG map that I embed inline to html using JQUERY Ajax. Then I use java script to address many elements in the svg object to change attributes and add event listeners based on data series. The data and the maps are interactive. Users can change state over many permutations.

I am also using JQUERY UI 1.10.3 Tabs

There are numerous categories. Therefore depending on the presentation there could be multiple copies of the svg map on the same Tab and many different Tabs having the same svg map.

Each copy of the svg document has the same element IDs. The problem I am experiencing is that the element IDs are conflicting with each other in the HTML DOM. So that the only elements that are addressable are the elements from the svg object that occurs first in the HTML document.

My question is:

Do I need to make the elements IDs unique for each instance of the svg object? Or is there a way to set scope and context to the svg object in the HTML DOM?

Example:

Each containing div has a unique ID that I use to insert the svg document and retrieve the SVG document.

    <div id="svgMap_div_1" class="cIG" style="height: 500px; width: 750px"></div>

Code used to load the svg document that is received from ajax (mimeType = "image/svg+xml"):

    document.getElementById('svgMap_div_1').appendChild(svgXMLObject.documentElement)

Code used to retrieve the svg document. The declaration for svgDoc is local to the function it is implemented in and is unique.

    var svgDoc = document.getElementById('svgMap_div_1').ownerDocument;

Code used to update the individual elements. When the identical svg objects (child elements have the same IDs) are in the same HTML DOM, then this javascript will always address the svg object that occurs first in the HTML DOM even if it is retrieved from a discrete div for example id='svgMap_div_2'.

    var svgElem = svgDoc.getElementById('elemID');
    svgElem.setAttribute("fill", '#0000FF');

I believe that the root of the problem is that svgDoc is retrieved using ownerDocument. Rather than create a discrete object with scope and context, it reaches through to the entire DOM so that the DOM is the context. See the code reference from the IDE:

HTMLDocument Node.ownerDocument

    Property ownerDocument http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html 
    See Also:
    Document
    Since:
    Standard ECMA-262 3rd. Edition
    Level 2 Document Object Model Core Definition.
    @type
    Document

I can develop a working algorithm to make all of the ids unique if I have to but I sure would like to find a better way. I have experimented with other methods and properties to get a working svg document object but none of these are working for me.

Below is a working example to demonstrate what happens when both svg objects are in different div on the same page. All four combinations change the color of the first rectangle. If you change the ID for the rect in the second object to 'box-b' and address it accordingly ('bbox_div', 'box-b'), then both rectangles are operated on independent from one another.

<script type='text/javascript'>
function colorBox(divID, svgElem, colorHex) {
    /* DOM  This is the original solution that did not work !!!*/
    //    var svgDoc = document.getElementById(divID).ownerDocument.
    //    var svgBox = svgDoc.getElementById(svgElem);
    //    svgBox.setAttribute("fill", colorHex);

    /* querySelector */
    //    document.querySelector("#"+divID + " ."+svgElem).setAttribute("fill", colorHex);

    /* jquery div */
    //    var svgBox = $('#'+divID+' svg').find('#'+svgElem);
    //    svgBox.css("fill", colorHex);    

    /* jquery class */
    $('#'+divID+' svg').find('.'+svgElem).css("fill", colorHex);
}
</script>
<div style="height: 100px; width: 100px">
    <a href="#" onclick="colorBox('abox_div', 'box-a', '#FF0000');return false;">Box A Red</a><br>
    <a href="#" onclick="colorBox('abox_div', 'box-a', '#0000FF');return false;">Box A Blue</a><br>
    <a href="#" onclick="colorBox('bbox_div', 'box-a', '#FF0000');return false;">Box B Red</a><br>
    <a href="#" onclick="colorBox('bbox_div', 'box-a', '#0000FF');return false;">Box B Blue</a><br -->
</div>
<div id="abox_div" style="height: 100px; width: 100px">
    <svg xmlns="http://www.w3.org/2000/svg"
         width="0.990919in"
         height="0.597218in" 
         viewBox="0 0 71.3461 42.9997">

    <style type="text/css">
    <![CDATA[
        .allboxes {fill:#00FF00}
    ]]>
    </style>

    <g class="box-a allboxes" transform="translate(0.24,-0.24)"  fill="#e8eef7">
        <rect x="0" y="0.48" width="70.8661" height="42.5197" ><title>Box A</title></rect>
    </g>
   </svg>
</div>
<div id="bbox_div" style="height: 100px; width: 100px">
   <svg xmlns="http://www.w3.org/2000/svg"
        width="0.990919in"
        height="0.597218in" 
        viewBox="0 0 71.3461 42.9997">
    <g class="box-a allboxes" transform="translate(0.24,-0.24)"  fill="#e8eef7">
        <rect x="0" y="0.48" width="70.8661" height="42.5197" ><title>Box B</title></rect>
    </g>
   </svg>
</div>
like image 518
Threadid Avatar asked Sep 06 '13 03:09

Threadid


People also ask

Is SVG part of the DOM?

The SVG DOM builds upon and is compatible with DOM Level 2. In particular: The SVG DOM requires complete support for DOM Level 2 Core [DOM2]

What is inline SVG?

Inline SVG simply refers to SVG markup that is included in the markup for a webpage.

What are the components of SVG?

Any element in the SVG namespace. One of the element types that can cause graphics to be drawn onto the target canvas. Specifically: 'audio', 'canvas', 'circle', 'ellipse', 'foreignObject', 'iframe', 'image', 'line', 'path', 'polygon', 'polyline', 'rect', 'text', 'textPath', 'tspan' and 'video'.


2 Answers

I believe it's strictly speaking invalid HTML if you have duplicate IDs. I'd suggest using some other mechanism of identifying the elements, either using classes instead of IDs or data- attributes. Then you could move from .getElemntById() to either .querySelector() (try on JS Bin):

<html>
<head>
<title></title>
</head>
<body>
  <div id="div1">
    <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px">
      <rect class="rect1" width="50px" height="50px"/>
    </svg>
  </div>
  <div id="div2">
    <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px">
      <rect class="rect1" width="50px" height="50px"/>
    </svg>
  </div>
  <script type="text/javascript">
    document.querySelector("#div1 .rect1").setAttribute("fill","red");
    document.querySelector("#div2 .rect1").setAttribute("fill","green");
  </script>
</body>
</html>

or .getElementsByClassName() (try on JS Bin):

<html>
  <head>
    <title></title>
  </head>
  <body>
    <div id="div1">
      <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px">
        <rect class="rect1" width="50px" height="50px"/>
      </svg>
    </div>
    <div id="div2">
      <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px">
        <rect class="rect1" width="50px" height="50px"/>
      </svg>
    </div>
    <script type="text/javascript">
      document.getElementById("div1").getElementsByClassName("rect1")[0].setAttribute("fill","red");
      document.getElementById("div2").getElementsByClassName("rect1")[0].setAttribute("fill","green");
    </script>
  </body>
</html>

Probably .querySelector() would also work if you leave the ID attributes as they are, like:

document.querySelector("#div1 [id=rect1]")

but I'd still suggest using data- attributes as this avoids any potential ID pitfalls.

like image 97
Thomas W Avatar answered Sep 20 '22 21:09

Thomas W


Since you are already using jQuery, you can use jQuery's selectors, which don't care about duplicate ids.

function colorBox(divID, svgElem, colorHex) {
  var svgBox = $('#'+divID+' svg').find('#'+svgElem);
  svgBox.css("fill", colorHex);
}

Demo here: http://jsfiddle.net/w9KA3/1/

like image 41
Paul LeBeau Avatar answered Sep 21 '22 21:09

Paul LeBeau