Alpine: Error when using `x-for` to generate SVG elements

Created on 7 Jul 2020  路  4Comments  路  Source: alpinejs/alpine

Alpine version: 2.4.1

I'm getting an error when using x-for on a template tag to generate SVG elements. With the following HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rectangle Editor</title>
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/alpine.js"></script>
</head>
<body>
    <div x-data="rectangleEditor()">
        <svg width="1024" height="1024">
            <!-- Error -->
            <template x-for="rectangle in rectangles" :key="rectangle">
                <rect :x="rectangle.x" :y="rectangle.y" :width="rectangle.width" :height="rectangle.height" />
            </template>
        </svg>
        <!-- No error -->
        <template x-for="rectangle in rectangles" :key="rectangle">
            <div :x="rectangle.x" :y="rectangle.y" :width="rectangle.width" :height="rectangle.height" />
        </template>
    </div>

    <script>
        function rectangleEditor() {
            return {
                rectangles: [
                    { x: 20, y: 40, width: 200, height: 400 },
                    { x: 60, y: 100, width: 500, height: 500 },
                    { x: 200, y: 40, width: 100, height: 400 },
                    { x: 300, y: 40, width: 100, height: 400 },
                ],
            }
        };
    </script>
</body>
</html>

I don't get such an error when using x-for outside a svg element. But as soon as I add elements in a svg element using x-for (even standard HTML elements such as div), I get the following stack trace:

Chromium 81:

alpine.js:76 Uncaught TypeError: Cannot read property 'childElementCount' of undefined
    at warnIfMalformedTemplate (alpine.js:76)
    at handleForDirective (alpine.js:438)
    at alpine.js:1570
    at Array.forEach (<anonymous>)
    at Component.resolveBoundAttributes (alpine.js:1530)
    at Component.initializeElement (alpine.js:1446)
    at alpine.js:1430
    at alpine.js:1420
    at walk (alpine.js:84)
    at walk (alpine.js:88)

Firefox 78:

Uncaught TypeError: el.content is undefined
    alpinejs 2.4.1/dist/alpine.js:76
    alpinejs 2.4.1/dist/alpine.js:438
    alpinejs 2.4.1/dist/alpine.js:1570
    alpinejs 2.4.1/dist/alpine.js:1530
    alpinejs 2.4.1/dist/alpine.js:1446
    alpinejs 2.4.1/dist/alpine.js:1430
    alpinejs 2.4.1/dist/alpine.js:1420
    alpinejs 2.4.1/dist/alpine.js:84
    alpinejs 2.4.1/dist/alpine.js:88
    alpinejs 2.4.1/dist/alpine.js:88
    alpinejs 2.4.1/dist/alpine.js:1408
    alpinejs 2.4.1/dist/alpine.js:1425
    alpinejs 2.4.1/dist/alpine.js:1355
    alpinejs 2.4.1/dist/alpine.js:1735
    alpinejs 2.4.1/dist/alpine.js:1678
    alpinejs 2.4.1/dist/alpine.js:1694
    alpinejs 2.4.1/dist/alpine.js:1693
    alpinejs 2.4.1/dist/alpine.js:1677

If this isn't supported, then this should be documented in the README. (I'm willing to open a PR for that if this is the case.)

documentation

Most helpful comment

Yeah, it's a DOM issue. templates inside an svg tag are not implemented and they miss the content property (you can see it using the inspector without alpine).

You need to include a polyfil like the one below (adapted from one mentioned on the polymer issue)

(function(){ 
  var templates = document.querySelectorAll('svg template');
  var el, template, attribs, attrib, count, child, content;
  for (var i=0; i<templates.length; i++) {
    el = templates[i];
    template = el.ownerDocument.createElement('template');
    el.parentNode.insertBefore(template, el);
    attribs = el.attributes;
    count = attribs.length;
    while (count-- > 0) {
      attrib = attribs[count];
      template.setAttribute(attrib.name, attrib.value);
      el.removeAttribute(attrib.name);
    }
    el.parentNode.removeChild(el);
    content = template.content;
    while ((child = el.firstChild)) {
      content.appendChild(child);
    }
  }
})();

All 4 comments

FWIW I suspect this is not an Alpine issue - if you are using HTML tags (like template) inside an SVG element they will have an SVG namespace so HTML elements like HTMLTemplateElement won't be implemented by the browser. I ran into something similar a while back trying to use vanilla JS and templates in SVG elements.

Edit: Sounds like Polymer ran into something similar and there is now a WHATWG feature request open to provide support: https://github.com/whatwg/html/issues/3563 - not much help in the short term though.

Yeah, it's a DOM issue. templates inside an svg tag are not implemented and they miss the content property (you can see it using the inspector without alpine).

You need to include a polyfil like the one below (adapted from one mentioned on the polymer issue)

(function(){ 
  var templates = document.querySelectorAll('svg template');
  var el, template, attribs, attrib, count, child, content;
  for (var i=0; i<templates.length; i++) {
    el = templates[i];
    template = el.ownerDocument.createElement('template');
    el.parentNode.insertBefore(template, el);
    attribs = el.attributes;
    count = attribs.length;
    while (count-- > 0) {
      attrib = attribs[count];
      template.setAttribute(attrib.name, attrib.value);
      el.removeAttribute(attrib.name);
    }
    el.parentNode.removeChild(el);
    content = template.content;
    while ((child = el.firstChild)) {
      content.appendChild(child);
    }
  }
})();

@SimoTod That polyfill seems to be working (tested in Firefox and Chromium). Thanks a lot :slightly_smiling_face:

No worries. Make sure you test it on all browsers you support (IE11, old edge, Safari, etc). It should work but I did not check. 馃憤

Was this page helpful?
0 / 5 - 0 ratings