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

Unbinding an event handler results in the following error: TypeError: function is not an event callback. #2498

Open
moepnse opened this issue Oct 17, 2024 · 0 comments

Comments

@moepnse
Copy link
Contributor

moepnse commented Oct 17, 2024

Hello,

unbinding an event handler that is a method of a web component instance does not work and results in an error.

Below is a demo of the problem.

To reproduce the error, you need to click on the red area of the page and a popup will appear. To close the popup, you have to click anywhere on the page. This should also result in the event handler being unregistered, but instead an error is displayed in the console.

Using removeEventListener instead of unbind will silently fail.

Thanks in advance!

Error

Traceback (most recent call last):
  File "C:/Users/test/issues/brython_issue_202401017_web_component.html#__main__", line 102, in close
    document.body.unbind("click", self.close)
TypeError: function is not an event callback

Issue Demo

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

        <title>Issue Demo</title>

        <style>
html, body {
    height: 100%;
}
.modal-in {
    displaY: block;
    border: 1px solid black;
    padding: 5px;
    background-color: rgb(112, 248, 112);
}

ui-popup {
    display: none;
    position: absolute;
}

#show_popup {
    background-color: red;
    font-weight: bold;
    padding: 5px;
    cursor: pointer;
}
        </style>

        <!-- 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


class BaseComponent:

    _registry = []
    _initialized = False
    _logic_obj = None
    _is_container: bool = False
    _observe_attributes = {"childList": True}
    _create_observer = False

    def __init__(self):
        # Create a shadow root
        shadow = self._shadow = self.attachShadow({'mode': 'open'})
        default_slot = self._default_slot = document.createElement("slot")
        shadow <= default_slot

    def mutation(self, records, observer):
        console.debug("mutation detected:", records)
        for record in records:
            for node in record.addedNodes:
                console.debug("node:", node)
                # NodeType 3 is a TextNode
                if node.nodeType in (3, 8):
                    continue
                self.append_child(node)

    def connectedCallback(self):
        if not self._initialized:
            self._initialized = True
        if self._create_observer:
            console.debug("creating observer for:", self)
            self._observer = observer = window.MutationObserver.new(self.mutation)
            observer.observe(self, self._observe_attributes)
        self.__bind_events__()
    
    def disconnectedCallback(self):
        self.__unbind_events__()

    def __bind_events__(self):
        pass

    def __unbind_events__(self):
        pass

    @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)

    def append_child(self, child):
        console.debug("child:", child)


class Popup(BaseComponent):

    def open(self, ev=None):
        if ev is not None:
            self.style.left = f"{ev.clientX}px"
            self.style.top = f"{ev.clientY}px"
            ev.stopPropagation()
        self.classList.add("modal-in")
        document.body.bind("click", self.close)

    def close(self, ev=None):
        if ev is not None:
            # does not work.
            document.body.unbind("click", self.close)
        self.classList.remove("modal-in")


BaseComponent.register()
        </script>
    </head>
    <body onload="brython({debug: 10})">
        <ui-popup id="popup1">TEST POPUP (Click anywhere on this page to close me.)</ui-popup>
        <div id="show_popup">Show popup (Click me!)</div>
        <script type="text/python">
from browser import window, console, document

document["show_popup"].bind("click", document["popup1"].open)
        </script>
    </body>
</html>
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

1 participant