Is there some innerHTML replacement in SVG/XML?

In HTML I can build a simple templating system by providing a template in form of a string, replace some parts of it and then assign it using innerHTML to some container.

var templ = '<span>{myText}</span>'
var newContent = templ.replace( '{myText}', someVariable );
document.querySelector( '#myContainer' ).innerHTML = newContent;

This way I can take advantage of the browser's HTML parser and do not have to repeatedly use document.createElement(). The later can be quite cumbersome, if the templates grows beyond a few elements.

In SVG, however, there is no property on the elements as innerHTML or even innerSVG for that matter.

So my question is: Is there anything I can use in SVG ro resemble the approach from the example above or am I stuck with document.createElement() (or respectivly some lib that uses it)?

As always with my questions: Vanilla JavaScript solutions are preferred, but any pointer to a lib providing a solution is appreciated.

Answers:

Answer

You can use DOMParser to parse XML: https://developer.mozilla.org/en/Parsing_and_serializing_XML you can then use importNode to get that into your existing document if you want: https://developer.mozilla.org/en/DOM/document.importNode to end up with something like this...

var doc = new DOMParser().parseFromString(
   '<svg xmlns="http://www.w3.org/2000/svg"><circle cx="100" cy="100" r="20"/></svg>',
   'application/xml');

someElement.appendChild(
 someElement.ownerDocument.importNode(doc.documentElement, true));
Answer

Check out the innerSVG javascript shim, it provides the functionality you want.

2014 update: The DOM parsing spec defines innerHTML and outerHTML on Element, which makes these available on svg and xml elements. This has been shipping in Blink for a while now, first versions to support this was Chrome 32 / Opera 19, more details can be found in this bugreport.

Answer

Here I write a dirty way...

innerHTML workaround for SVG

http://jsfiddle.net/microbians/8ztNU/

<html>
    <body>
        <svg id="svgcanvas">
        </svg>
        <script>
            var twocircles='<circle cx="253" cy="562" r="10" stroke="black" stroke-width="2" fill="red"></circle> \
            <circle cx="353" cy="562" r="10" stroke="black" stroke-width="2" fill="red"></circle>'
            var receptacle = document.createElement('div');
            var svgfragment='<svg>'+twocircles+'</svg>';
            receptacle.innerHTML=''+svgfragment;
            var Nodes=Array.prototype.slice.call(receptacle.childNodes[0].childNodes);
            Nodes.forEach(function(el){document.getElementById('svgcanvas').appendChild(el)})
        </script>
    </body>
</html>

Enjoy!

Answer

How about my innerSVG shim? CoffeeScript source is below, compiled JavaScript is on https://gist.github.com/2648095

# Important: You must serve your pages as XHTML for this shim to work,
# otherwise namespaced attributes and elements will get messed up.
Object.defineProperty SVGElement.prototype, 'innerHTML',
  get: () ->
    $temp = document.createElement 'div'
    $node = @cloneNode true

    for $child in $node.children
      $temp.appendChild $child

    return $temp.innerHTML

  set: (markup) ->
    while @firstChild
      @firstChild.parentNode.removeChild @firstChild

    markup = "<svg id='wrapper' xmlns='http://www.w3.org/2000/svg'>#{markup}</svg>"
    $div = document.createElement 'div'
    $div.innerHTML = markup
    $svg = $div.querySelector 'svg#wrapper'

    for $element in $svg.children
      @appendChild $element 
  enumerable : false  
  configurable : true
Answer

With jQuery, you can do it this way:

Let's suppose your svgString contains your svg image after the replacing operations.

$(svgString)[0] to create a svg tag corresponding to your string. Then you can append this element where you want in the dom to draw the image.

I hope this helps

Answer

It looks like at least on Chrome and Safari, you can wrap your SVG element in an HTML element and ask for innerHTML. The following HTML file renders a red rectangle, even though the SVG source specifies green.

<body>
<div id="wrapper">
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="120" height="120" viewBox="0 0 236 120">
    <rect x="14" y="23" width="250" height="50" fill="green" stroke="black" stroke-width="1" />
  </svg>
</div>
<script>
  var el = document.getElementById('wrapper');
  var t = el.innerHTML;
  t = t.replace(/green/g, "red");
  el.innerHTML = t;
</script>
</body>
Answer

More simply, document.querySelector('#myContainer').textContent = newContent; has pretty good support, def. to IE9+, Safari, FF, Chrome.

Mike Bostock's my hero for these kinds of things: the D3.js source code is my go-to for SVG questions.

Answer

The short answer is "No, there is nothing equivalent in the world of XML that lets you hand it a bit of markup and have it automatically create all the elements and attributes in the proper namespaces for the location where you insert it."

The closest direct answer is what @Robert has. As noted in my comments, even then you'll need to create any snippets inside an SVG document that has the same namespaces and prefixes as the document into which you'll be inserting the fragment.

Instead, you might find it is as easy (or easier) to use a convenience method on the standard DOM methods:

// Create a named SVG element on a node, with attributes and optional text
function appendTo(node,name,attrs,text){
  var p,ns=appendTo.ns,svg=node,doc=node.ownerDocument;
  if (!ns){ // cache namespaces by prefix once
    while (svg&&svg.tagName!='svg') svg=svg.parentNode;
    ns=appendTo.ns={svg:svg.namespaceURI};
    for (var a=svg.attributes,i=a.length;i--;){
      if (a[i].namespaceURI) ns[a[i].localName]=a[i].nodeValue;
    }
  }
  var el = doc.createElementNS(ns.svg,name);
  for (var attr in attrs){
    if (!attrs.hasOwnProperty(attr)) continue;
    if (!(p=attr.split(':'))[1]) el.setAttribute(attr,attrs[attr]);
    else el.setAttributeNS(ns[p[0]]||null,p[1],attrs[attr]);
  }
  if (text) el.appendChild(doc.createTextNode(text));
  return node.appendChild(el);
}

function clear(node){
  while (node.lastChild) node.removeChild(node.lastChild);
}

With this you can do things like:

var icons={
  Apps  : "/images/apps.png",
  Games : "/images/games.png"
}
var wrap = document.querySelector('#container');
clear(wrap);

for (var label in icons){
  if (!icons.hasOwnProperty(label)) continue;
  var icon = appendTo(wrap,'g',{'class':'icon'});
  appendTo(icon,'image',{'xlink:href':icons[label]});
  appendTo(icon,'text',{x:10,y:20},label);
}

This is IMHO cleaner than trying to construct the raw SVG markup using string concatenation:

var svg = [];
for (var label in icons){
  if (!icons.hasOwnProperty(label)) continue;
  svg.push('<g class="icon">');
  svg.push('<image xlink:href="'+icons[label]+'" />');
  svg.push('<text x="10" y="20">'+label+'</text>');
  svg.push('</g>');
}
wrap.innerSVG = svg.join(''); // doesn't work, of course

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.