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>
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 ofunbind
will silently fail.Thanks in advance!
Error
Issue Demo