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

If the Web Component has a shadow DOM, iterating over childNodes will return only text nodes #2469

Open
moepnse opened this issue Jun 30, 2024 · 2 comments

Comments

@moepnse
Copy link
Contributor

moepnse commented Jun 30, 2024

Hi!

If self._childs_root is a shadow DOM, then only text nodes are returned when iterating over self.childNodes. A default slot is missing on purpose.

I tried the same with JavaScript in the developer console and it returns every node inside the web component. Is this a bug or am I missing something?

Thanks in advance!

<!DOCTYPE html>
<html>
    <head>
        <!-- Required meta tags-->
        <meta charset="utf-8">

        <title>Issue Demo</title>

        <!-- Brython -->
        <script src="https://raw.githack.com/brython-dev/brython/master/www/src/brython.js"></script>
        <script src="https://raw.githack.com/brython-dev/brython/master/www/src/brython_stdlib.js"></script>
        <script type="text/python">
import copy
from browser import webcomponent, html, window, console, document, aio


class BaseComponent:

    _registry = []
    _initialized = False
    _logic_obj = None
    _is_container: bool = False
    _observed_attributes = []

    def __init__(self):
        # Create a shadow root
        shadow = self._shadow = self.attachShadow({'mode': 'open'})
        self._childs_root = self._shadow
        #self._childs_root = self

    @property
    def observedAttributes(self):
        return self._observed_attributes

    def attributeChangedCallback(self, name, old, new, ns):
        print(f"attribute {name} changed from {old} to {new}")

    def _append_childs(self):
        console.debug("append_childs", self, self.childNodes)
        root = self._childs_root
        # If root is a shadow DOM, then only TextNodes are returned from the iterator.
        for child in self.childNodes:
            console.debug("appending child", child, "to", root)
            root.appendChild(child)

    def connectedCallback(self):
        if not self._initialized:
            self._append_childs()
            self._initialized = True

    @staticmethod
    def un_camel(word: str) -> str:
        upper_chars: str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        last_char: str = word[0]
        output: list = [last_char.lower()]
        for c in word[1:]:
            if c == "_":
                output.append("-")
                continue
            if c in upper_chars:
                if last_char not in upper_chars:
                    output.append('-')
                output.append(c.lower())
            else:
                output.append(c)
            last_char = c
        return "".join(output)

    @classmethod
    def __init_subclass__(cls, **kwargs):
        BaseComponent._registry.append(cls)

    @classmethod
    def remove_from_registry(cls, component):
        print(cls._registry, component)
        if component in cls._registry:
            cls._registry.remove(component)

    @classmethod
    def register(cls):
        registry = cls._registry
        for web_component in registry:
            web_component_name = cls.un_camel(web_component.__name__)
            component_name = f"ui-{web_component_name}"
            console.debug(f"registering web component {web_component} as {component_name}...")
            webcomponent.define(component_name, web_component)


class Page(BaseComponent):
    pass


class Icon(BaseComponent):

    def connectedCallback(self):
        if not self._initialized:
            self._initialized = True


BaseComponent.register()
        </script>
    </head>
    <body onload="brython({debug: 10})">
        <ui-page>
            text node 1 
           <ui-icon id="icon" class="demo">save</ui-icon>
           text node 2
           <!-- comment -->
        </ui-page>
    </body>
</html>
@moepnse
Copy link
Contributor Author

moepnse commented Jul 1, 2024

Here is an equivalent example written in JavaScript that behaves as expected.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <script type="text/javascript">
window.onload = function() {
    class FooBar extends HTMLElement {
        constructor(){
            super();
            this.shadow = this.attachShadow({ mode: 'open' })
        }
        connectedCallback() {
            for (var i = 0; i < this.childNodes.length; i++) {
                console.debug(this.childNodes[i]);
            }
        }
    }

    customElements.define('foo-bar', FooBar);

    class UIIcon extends HTMLElement {
        constructor(){
            super();
            this.shadow = this.attachShadow({ mode: 'open' })
        }
    }

    customElements.define('ui-icon', UIIcon);
}
      </script> 
  </head>
  <body>
    <foo-bar>
      <h1>Test</h1>
      <ui-icon></ui-icon>
    </foo-bar>
</body>

@PierreQuentel
Copy link
Contributor

Hello !

In _append_childs() it seems that root.appendChild(child) removes the child from self.childNodes (I didn't test but it's probably the same in Javascript) ; if you remove it, the 5 child nodes are printed.

I think the problem can be solved by replacing for child in self.childNodes: by for child in list(self.childNodes):

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

No branches or pull requests

2 participants