Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when using x-for to generate SVG elements #637

Closed
Calinou opened this issue Jul 7, 2020 · 8 comments · Fixed by #638
Closed

Error when using x-for to generate SVG elements #637

Calinou opened this issue Jul 7, 2020 · 8 comments · Fixed by #638
Labels
documentation Improvements or additions to documentation

Comments

@Calinou
Copy link
Contributor

Calinou commented Jul 7, 2020

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/alpinejs@2.4.1/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.)

@allmarkedup
Copy link
Contributor

allmarkedup commented Jul 7, 2020

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: whatwg/html#3563 - not much help in the short term though.

@SimoTod
Copy link
Collaborator

SimoTod commented Jul 7, 2020

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);
    }
  }
})();

@Calinou
Copy link
Contributor Author

Calinou commented Jul 7, 2020

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

@SimoTod
Copy link
Collaborator

SimoTod commented Jul 7, 2020

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. 👍

Calinou added a commit to Calinou/alpine that referenced this issue Jul 7, 2020
This closes alpinejs#637.

Co-authored-by: Simone Todaro <simone.todaro@gear4music.com>
@HugoDF HugoDF added the documentation Improvements or additions to documentation label Jul 7, 2020
Calinou added a commit to Calinou/alpine that referenced this issue Jul 16, 2020
This closes alpinejs#637.

Co-authored-by: Simone Todaro <simone.todaro@gear4music.com>
Calinou added a commit to Calinou/alpine that referenced this issue Jul 16, 2020
This closes alpinejs#637.

Co-authored-by: Simone Todaro <simone.todaro@gear4music.com>
@Corkle
Copy link

Corkle commented Dec 19, 2020

I'm curious why x-for is only supported on template tags. Is this due to a technical limitation? It would be nice to be able to use it on other tag types that are supported within svg.

@Calinou
Copy link
Contributor Author

Calinou commented Dec 19, 2020

Is this due to a technical limitation?

Yes, Alpine.js doesn't use a virtual DOM which means you can only use x-for on template tags.

@HugoDF
Copy link
Contributor

HugoDF commented Dec 20, 2020

Is this due to a technical limitation?

Yes, Alpine.js doesn't use a virtual DOM which means you can only use x-for on template tags.

Hmmm I don't think it's strictly VDOM vs not, I think it's a trade-off (admittedly partly due to no VDOM). You probably could have an x-for without template but you would need to hide the relevant Nodes/elements until x-for has run. The big benefits of template is that by default it doesn't show visually and is easy to use to generate DOM Nodes.

@ocharles
Copy link

ocharles commented May 21, 2022

Edit: sorry, you can ignore this comment. I was applying the polyfill too early.

Here's an updated example that works:

<!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/alpinejs@2.4.1/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 },
                ],
            }
        };

      (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);
          }
        }
      })();
    </script>
</body>
</html>

For posterity, here's my original comment:

Can anyone supply a small example page using `x-for` with `svg` and the latest Alpine? With the polyfill above, I still get errors.

This is what I'm trying at the moment:

<!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/alpinejs@2.4.1/dist/alpine.js"></script>
    <script>
      (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);
          }
        }
      })();
    </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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants