Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SVG fragments are mistakenly parsed as HTMLUnknownElement

I have trouble composing an inline-SVG using Javascript.
The problem can be reduced to the following code (live example here):

Somewhere inside the body:

<svg id="drawing" xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>

Inside onReady:

$("#drawing").append($("<rect style='fill: blue' width='100' height='100' />"));

I expected to see a blue rectangle now. However, Chrome and Firefox don't show anything.

Using Firebug, I found out that Firefox interprets the "rect" as a HTMLUnknownElement (and not as a SVGElement).
If I choose "Edit SVG" on the SVG element (using Firebug) and insert a whitespace somewhere, the SVG seems to be reparsed and the rectangle appears.

How can I tell the parser to parse this fragment correctly?

like image 408
Matthias Avatar asked Nov 30 '25 14:11

Matthias


2 Answers

This example shows how to embed SVG in XHTML, including the programmatic creation of new SVG elements: http://phrogz.net/svg/svg_in_xhtml5.xhtml

This example shows how to use XHR to fetch SVG as XML, find a fragment of it, and two ways convert it into the local document before appending the node to the existing SVG document: http://phrogz.net/svg/fetch_fragment.svg

In general:

  1. Don't use jQuery with SVG directly.
  2. Dynamically-created SVG elements must be created using createElementNS, supplying the SVG namespace URI 'http://www.w3.org/2000/svg'. (Note, however, that the SVG attributes should not be created with a namespace.)
  3. You need to be sure to serve your XHTML as XML (content-type: application/xhtml+xml) and not as text/html.

Here's a general-purpose function I use on occasion for creating SVG elements conveniently. It works both within SVG documents as well as SVG-in-XHTML, allows for text content to be created directly, and supports namespaced attributes (such as xlink:href).

// Example usage:
//   var parentNode = findElementInTheSVGDocument();
//   var r = createOn( parentNode, 'rect', {
//     x:12, width:10, height:10, 'fill-opacity':0.3
//   });
function createOn(root,name,attrs,text){
  var doc = root.ownerDocument;
  var svg = root;
  while (svg.tagName!='svg') svg=svg.parentNode;
  var svgNS = svg.getAttribute('xmlns');
  var el = doc.createElementNS(svgNS,name);
  for (var attr in attrs){
    if (attrs.hasOwnProperty(attr)){
      var parts = attr.split(':');
      if (parts[1]) el.setAttributeNS(svg.getAttribute('xmlns:'+parts[0]),parts[1],attrs[attr]);
      else el.setAttributeNS(null,attr,attrs[attr]);
    }
  }
  if (text) el.appendChild(document.createTextNode(text));
  return root.appendChild(el);
} 
like image 155
Phrogz Avatar answered Dec 03 '25 03:12

Phrogz


I'm afraid it's not that easy:

  1. jsfiddle is not the place to test svg, it sends wrong content-type
  2. references to external js-files can't be created the html-way(always keep in mind, svg doesn't have to do anything with html)
  3. jquery uses some dummy-div's for creating the elements when using append(), but svg doesn't know div-elements
  4. also note: binding's to the load-event of a svg-document with jQuery doesn't seem to work

Here an example-code, works for me in FF when delivered as image/svg+xml


<svg id="drawing" 
     xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1"  
     onload="fx()">
  <script type="text/ecmascript" xlink:href="http://code.jquery.com/jquery-latest.js" />
  <script type="text/ecmascript">
  function fx()
  {
   $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'))
     .css('fill','blue')
     .attr({'width':100,'height':100})
     .appendTo('#drawing');
   }
  </script>  
</svg>

But like Marcin I would suggest to use a plugin.

To add from the parent document you may use an object containing the properties of the element, basic example:


<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script  type="text/javascript">
/*<![CDATA[*/

function fx(obj,params)
{
  var svgDoc=obj.contentDocument;
  if(typeof params.name!='string')return;
  var props=$.extend({'attrs':{},'style':{},'selector':null},params);
      props.target=(!props.selector)?svgDoc.documentElement:$(svgDoc).find(props.selector)

    $(svgDoc.createElementNS('http://www.w3.org/2000/svg', props.name))
     .css(props.style)
     .attr(props.attrs)
     .appendTo(props.target);
}

/*]]>*/
</script>
</head>
<body>
  <object onload="fx(this,{'name':'rect','attrs':{'width':100,'height':100},'style':{'fill':'blue'},'selector':'#drawing'})" 
          data="my.svg" 
          type="image/svg+xml" 
          width="200" 
          height="200">
    <param name="src" value="my.svg">
  </object>
</body> 
</html>

The structure of the object:

  • name:tagName(string)
  • attrs:attributes(object)
  • style:style(object)
  • selector:selector(string, if omitted the root-element will be selected)
like image 45
Dr.Molle Avatar answered Dec 03 '25 04:12

Dr.Molle



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!