mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-19 07:01:11 -05:00
The all-new, pyscript.web (ignore the branch name :) ) (#2129)
* Minor cleanups: move all Element classes to bottom of module. * Commenting. * Commenting. * Commenting. * Group dunder methods. * Don't cache the element's parent. * Remove style type check until we decide whether or not to add for classes too. * Add ability to register/unregister element classes. * Implement __iter__ for container elements. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Minor renaming to make it clear when we have an Element instance vs an actual DOM element. * remove duplication: added Element.get_tag_name * Commenting. * Allow Element.append to 1) use *args, 2) accept iterables * Remove iterable check - inteferes with js proxies. * Don't use *args, so it quacks more like a list ;) * Element.append take 2 :) * Remove unused code. * Move to web.py with a page object! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added 'page.title' too :) * Add __getitem__ as a shortcut for page.find * Add Element.__getitem__ to be consistent * Make __getitem__ consistent for Page, Element and ElementCollection. * Docstringing. * Docstringing. * Docstringing/commenting. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix select.add (revert InnerHTML->html) * Commenting. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Hand-edit some of the AI :) * Rename ElementCollection.children -> ElementCollection.elements * Remove unnecessary guard. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,7 @@ def when(event_type=None, selector=None):
|
||||
|
||||
def decorator(func):
|
||||
|
||||
from pyscript.web.elements import Element, ElementCollection
|
||||
from pyscript.web import Element, ElementCollection
|
||||
|
||||
if isinstance(selector, str):
|
||||
elements = document.querySelectorAll(selector)
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
try:
|
||||
from typing import Any
|
||||
"""Lightweight interface to the DOM and HTML elements."""
|
||||
|
||||
except ImportError:
|
||||
Any = "Any"
|
||||
|
||||
try:
|
||||
import warnings
|
||||
|
||||
except ImportError:
|
||||
# TODO: For now it probably means we are in MicroPython. We should figure
|
||||
# out the "right" way to handle this. For now we just ignore the warning
|
||||
# and logging to console
|
||||
class warnings:
|
||||
@staticmethod
|
||||
def warn(*args, **kwargs):
|
||||
print("WARNING: ", *args, **kwargs)
|
||||
# `when` is not used in this module. It is imported here save the user an additional
|
||||
# import (i.e. they can get what they need from `pyscript.web`).
|
||||
from pyscript import document, when # NOQA
|
||||
|
||||
|
||||
from pyscript import document
|
||||
def wrap_dom_element(dom_element):
|
||||
"""Wrap an existing DOM element in an instance of a subclass of `Element`.
|
||||
|
||||
This is just a convenience function to avoid having to import the `Element` class
|
||||
and use its class method.
|
||||
"""
|
||||
|
||||
return Element.wrap_dom_element(dom_element)
|
||||
|
||||
|
||||
class Element:
|
||||
# A lookup table to get an `Element` subclass by tag name. Used when wrapping an
|
||||
# existing DOM element.
|
||||
element_classes_by_tag_name = {}
|
||||
|
||||
@classmethod
|
||||
def from_dom_element(cls, dom_element):
|
||||
"""Create an instance of a subclass of `Element` for a DOM element."""
|
||||
def get_tag_name(cls):
|
||||
"""Return the HTML tag name for the class.
|
||||
|
||||
element_cls = ELEMENT_CLASSES_BY_TAG_NAME.get(dom_element.tagName.lower())
|
||||
For classes that have a trailing underscore (because they clash with a Python
|
||||
keyword or built-in), we remove it to get the tag name. e.g. for the `input_`
|
||||
class, the tag name is `input`.
|
||||
|
||||
# For any unknown elements (custom tags etc.) create an instance of this
|
||||
# class ('Element').
|
||||
if not element_cls:
|
||||
element_cls = cls
|
||||
"""
|
||||
return cls.__name__.replace("_", "")
|
||||
|
||||
@classmethod
|
||||
def register_element_classes(cls, element_classes):
|
||||
"""Register an iterable of element classes."""
|
||||
for element_class in element_classes:
|
||||
tag_name = element_class.get_tag_name()
|
||||
cls.element_classes_by_tag_name[tag_name] = element_class
|
||||
|
||||
@classmethod
|
||||
def unregister_element_classes(cls, element_classes):
|
||||
"""Unregister an iterable of element classes."""
|
||||
for element_class in element_classes:
|
||||
tag_name = element_class.get_tag_name()
|
||||
cls.element_classes_by_tag_name.pop(tag_name, None)
|
||||
|
||||
@classmethod
|
||||
def wrap_dom_element(cls, dom_element):
|
||||
"""Wrap an existing DOM element in an instance of a subclass of `Element`.
|
||||
|
||||
We look up the `Element` subclass by the DOM element's tag name. For any unknown
|
||||
elements (custom tags etc.) use *this* class (`Element`).
|
||||
"""
|
||||
element_cls = cls.element_classes_by_tag_name.get(
|
||||
dom_element.tagName.lower(), cls
|
||||
)
|
||||
|
||||
return element_cls(dom_element=dom_element)
|
||||
|
||||
@@ -41,16 +65,33 @@ class Element:
|
||||
Otherwise, we are being called to *wrap* an existing DOM element.
|
||||
"""
|
||||
self._dom_element = dom_element or document.createElement(
|
||||
type(self).__name__.replace("_", "")
|
||||
type(self).get_tag_name()
|
||||
)
|
||||
|
||||
self._parent = None
|
||||
# A set-like interface to the element's `classList`.
|
||||
self._classes = Classes(self)
|
||||
|
||||
# A dict-like interface to the element's `style` attribute.
|
||||
self._style = Style(self)
|
||||
|
||||
# Set any specified classes, styles, and DOM properties.
|
||||
self.update(classes=classes, style=style, **kwargs)
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check for equality by comparing the underlying DOM element."""
|
||||
return isinstance(obj, Element) and obj._dom_element == self._dom_element
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get an item within the element's children.
|
||||
|
||||
If `key` is an integer or a slice we use it to index/slice the element's
|
||||
children. Otherwise, we use `key` as a query selector.
|
||||
"""
|
||||
if isinstance(key, int) or isinstance(key, slice):
|
||||
return self.children[key]
|
||||
|
||||
return self.find(key)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# This allows us to get attributes on the underlying DOM element that clash
|
||||
# with Python keywords or built-ins (e.g. the output element has an
|
||||
@@ -80,119 +121,102 @@ class Element:
|
||||
|
||||
setattr(self._dom_element, name, value)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
"""Return the element's children as an `ElementCollection`."""
|
||||
return ElementCollection.wrap_dom_elements(self._dom_element.children)
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
"""Return the element's `classList` as a `Classes` instance."""
|
||||
return self._classes
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
"""Return the element's `parent `Element`."""
|
||||
if self._dom_element.parentElement is None:
|
||||
return None
|
||||
|
||||
return Element.wrap_dom_element(self._dom_element.parentElement)
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
"""Return the element's `style` attribute as a `Style` instance."""
|
||||
return self._style
|
||||
|
||||
def append(self, *items):
|
||||
"""Append the specified items to the element."""
|
||||
for item in items:
|
||||
if isinstance(item, Element):
|
||||
self._dom_element.appendChild(item._dom_element)
|
||||
|
||||
elif isinstance(item, ElementCollection):
|
||||
for element in item:
|
||||
self._dom_element.appendChild(element._dom_element)
|
||||
|
||||
# We check for list/tuple here and NOT for any iterable as it will match
|
||||
# a JS Nodelist which is handled explicitly below.
|
||||
# NodeList.
|
||||
elif isinstance(item, list) or isinstance(item, tuple):
|
||||
for child in item:
|
||||
self.append(child)
|
||||
|
||||
else:
|
||||
# In this case we know it's not an Element or an ElementCollection, so
|
||||
# we guess that it's either a DOM element or NodeList returned via the
|
||||
# ffi.
|
||||
try:
|
||||
# First, we try to see if it's an element by accessing the 'tagName'
|
||||
# attribute.
|
||||
item.tagName
|
||||
self._dom_element.appendChild(item)
|
||||
|
||||
except AttributeError:
|
||||
try:
|
||||
# Ok, it's not an element, so let's see if it's a NodeList by
|
||||
# accessing the 'length' attribute.
|
||||
item.length
|
||||
for element_ in item:
|
||||
self._dom_element.appendChild(element_)
|
||||
|
||||
except AttributeError:
|
||||
# Nope! This is not an element or a NodeList.
|
||||
raise TypeError(
|
||||
f'Element "{item}" is a proxy object, "'
|
||||
f"but not a valid element or a NodeList."
|
||||
)
|
||||
|
||||
def clone(self, clone_id=None):
|
||||
"""Make a clone of the element (clones the underlying DOM object too)."""
|
||||
clone = Element.wrap_dom_element(self._dom_element.cloneNode(True))
|
||||
clone.id = clone_id
|
||||
return clone
|
||||
|
||||
def find(self, selector):
|
||||
"""Find all elements that match the specified selector.
|
||||
|
||||
Return the results as a (possibly empty) `ElementCollection`.
|
||||
"""
|
||||
return ElementCollection.wrap_dom_elements(
|
||||
self._dom_element.querySelectorAll(selector)
|
||||
)
|
||||
|
||||
def show_me(self):
|
||||
"""Convenience method for 'element.scrollIntoView()'."""
|
||||
self._dom_element.scrollIntoView()
|
||||
|
||||
def update(self, classes=None, style=None, **kwargs):
|
||||
"""Update the element with the specified classes, styles, and DOM properties."""
|
||||
|
||||
if classes:
|
||||
self.classes.add(classes)
|
||||
|
||||
if isinstance(style, dict):
|
||||
if style:
|
||||
self.style.set(**style)
|
||||
|
||||
elif style is not None:
|
||||
raise ValueError(
|
||||
f"Style should be a dictionary, received {style} "
|
||||
f"(type {type(style)}) instead."
|
||||
)
|
||||
|
||||
self._set_dom_properties(**kwargs)
|
||||
|
||||
def _set_dom_properties(self, **kwargs):
|
||||
"""Set the specified DOM properties.
|
||||
|
||||
Args:
|
||||
**kwargs: The properties to set
|
||||
"""
|
||||
for name, value in kwargs.items():
|
||||
setattr(self, name, value)
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check for equality by comparing the underlying DOM element."""
|
||||
return isinstance(obj, Element) and obj._dom_element == self._dom_element
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return ElementCollection(
|
||||
[Element.from_dom_element(el) for el in self._dom_element.children]
|
||||
)
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._classes
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self._parent:
|
||||
return self._parent
|
||||
|
||||
if self._dom_element.parentElement:
|
||||
self._parent = Element.from_dom_element(self._dom_element.parentElement)
|
||||
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
return self._style
|
||||
|
||||
def append(self, child):
|
||||
if isinstance(child, Element):
|
||||
self._dom_element.appendChild(child._dom_element)
|
||||
|
||||
elif isinstance(child, ElementCollection):
|
||||
for el in child:
|
||||
self._dom_element.appendChild(el._dom_element)
|
||||
|
||||
else:
|
||||
# In this case we know it's not an Element or an ElementCollection, so we
|
||||
# guess that it's either a DOM element or NodeList returned via the ffi.
|
||||
try:
|
||||
# First, we try to see if it's an element by accessing the 'tagName'
|
||||
# attribute.
|
||||
child.tagName
|
||||
self._dom_element.appendChild(child)
|
||||
|
||||
except AttributeError:
|
||||
try:
|
||||
# Ok, it's not an element, so let's see if it's a NodeList by
|
||||
# accessing the 'length' attribute.
|
||||
child.length
|
||||
for element_ in child:
|
||||
self._dom_element.appendChild(element_)
|
||||
|
||||
except AttributeError:
|
||||
# Nope! This is not an element or a NodeList.
|
||||
raise TypeError(
|
||||
f'Element "{child}" is a proxy object, "'
|
||||
f"but not a valid element or a NodeList."
|
||||
)
|
||||
|
||||
def clone(self, clone_id=None):
|
||||
"""Make a clone of the element (clones the underlying DOM object too)."""
|
||||
clone = Element.from_dom_element(self._dom_element.cloneNode(True))
|
||||
clone.id = clone_id
|
||||
return clone
|
||||
|
||||
def find(self, selector):
|
||||
"""Return an ElementCollection representing all the child elements that
|
||||
match the specified selector.
|
||||
|
||||
Args:
|
||||
selector (str): A string containing a selector expression
|
||||
|
||||
Returns:
|
||||
ElementCollection: A collection of elements matching the selector
|
||||
"""
|
||||
return ElementCollection(
|
||||
[
|
||||
Element.from_dom_element(dom_element)
|
||||
for dom_element in self._dom_element.querySelectorAll(selector)
|
||||
]
|
||||
)
|
||||
|
||||
def show_me(self):
|
||||
"""Scroll the element into view."""
|
||||
self._dom_element.scrollIntoView()
|
||||
|
||||
|
||||
class Classes:
|
||||
"""A set-like interface to an element's `classList`."""
|
||||
@@ -233,6 +257,7 @@ class Classes:
|
||||
return " ".join(self._class_list)
|
||||
|
||||
def add(self, *class_names):
|
||||
"""Add one or more classes to the element."""
|
||||
for class_name in class_names:
|
||||
if isinstance(class_name, list):
|
||||
for item in class_name:
|
||||
@@ -242,9 +267,11 @@ class Classes:
|
||||
self._class_list.add(class_name)
|
||||
|
||||
def contains(self, class_name):
|
||||
"""Check if the element has the specified class."""
|
||||
return class_name in self
|
||||
|
||||
def remove(self, *class_names):
|
||||
"""Remove one or more classes from the element."""
|
||||
for class_name in class_names:
|
||||
if isinstance(class_name, list):
|
||||
for item in class_name:
|
||||
@@ -254,10 +281,12 @@ class Classes:
|
||||
self._class_list.remove(class_name)
|
||||
|
||||
def replace(self, old_class, new_class):
|
||||
"""Replace one of the element's classes with another."""
|
||||
self.remove(old_class)
|
||||
self.add(new_class)
|
||||
|
||||
def toggle(self, *class_names):
|
||||
"""Toggle one or more of the element's classes."""
|
||||
for class_name in class_names:
|
||||
if class_name in self:
|
||||
self.remove(class_name)
|
||||
@@ -274,6 +303,7 @@ class HasOptions:
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Return the element's options as an `Options"""
|
||||
if not hasattr(self, "_options"):
|
||||
self._options = Options(self)
|
||||
|
||||
@@ -281,63 +311,17 @@ class HasOptions:
|
||||
|
||||
|
||||
class Options:
|
||||
"""This class represents the <option>s of a <datalist>, <optgroup> or <select>
|
||||
element.
|
||||
"""This class represents the <option>s of a <datalist>, <optgroup> or <select>.
|
||||
|
||||
It allows to access to add and remove <option>s by using the `add` and `remove`
|
||||
methods.
|
||||
It allows access to add and remove <option>s by using the `add`, `remove` and
|
||||
`clear` methods.
|
||||
"""
|
||||
|
||||
def __init__(self, element: Element) -> None:
|
||||
def __init__(self, element):
|
||||
self._element = element
|
||||
|
||||
def add(
|
||||
self,
|
||||
value: Any = None,
|
||||
html: str = None,
|
||||
text: str = None,
|
||||
before: Element | int = None,
|
||||
**kws,
|
||||
) -> None:
|
||||
"""Add a new option to the select element"""
|
||||
|
||||
option = document.createElement("option")
|
||||
if value is not None:
|
||||
kws["value"] = value
|
||||
if html is not None:
|
||||
option.innerHTML = html
|
||||
if text is not None:
|
||||
kws["text"] = text
|
||||
|
||||
for key, value in kws.items():
|
||||
option.setAttribute(key, value)
|
||||
|
||||
if before:
|
||||
if isinstance(before, Element):
|
||||
before = before._dom_element
|
||||
|
||||
self._element._dom_element.add(option, before)
|
||||
|
||||
def remove(self, item: int) -> None:
|
||||
"""Remove the option at the specified index"""
|
||||
self._element._dom_element.remove(item)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all the options"""
|
||||
for i in range(len(self)):
|
||||
self.remove(0)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Return the list of options"""
|
||||
return [
|
||||
Element.from_dom_element(opt) for opt in self._element._dom_element.options
|
||||
]
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
"""Return the selected option"""
|
||||
return self.options[self._element._dom_element.selectedIndex]
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.options
|
||||
@@ -348,14 +332,49 @@ class Options:
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
@property
|
||||
def options(self):
|
||||
"""Return the list of options."""
|
||||
return [Element.wrap_dom_element(o) for o in self._element._dom_element.options]
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
"""Return the selected option."""
|
||||
return self.options[self._element._dom_element.selectedIndex]
|
||||
|
||||
def add(self, value=None, html=None, text=None, before=None, **kwargs):
|
||||
"""Add a new option to the element"""
|
||||
if value is not None:
|
||||
kwargs["value"] = value
|
||||
|
||||
if html is not None:
|
||||
kwargs["innerHTML"] = html
|
||||
|
||||
if text is not None:
|
||||
kwargs["text"] = text
|
||||
|
||||
new_option = option(**kwargs)
|
||||
|
||||
if before:
|
||||
if isinstance(before, Element):
|
||||
before = before._dom_element
|
||||
|
||||
self._element._dom_element.add(new_option._dom_element, before)
|
||||
|
||||
def clear(self):
|
||||
"""Remove all options."""
|
||||
while len(self) > 0:
|
||||
self.remove(0)
|
||||
|
||||
def remove(self, index):
|
||||
"""Remove the option at the specified index."""
|
||||
self._element._dom_element.remove(index)
|
||||
|
||||
|
||||
class Style:
|
||||
"""A dict-like interface to an element's css style."""
|
||||
"""A dict-like interface to an element's `style` attribute."""
|
||||
|
||||
def __init__(self, element: Element) -> None:
|
||||
def __init__(self, element: Element):
|
||||
self._element = element
|
||||
self._style = self._element._dom_element.style
|
||||
|
||||
@@ -366,9 +385,11 @@ class Style:
|
||||
self._style.setProperty(key, value)
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove a CSS property from the element."""
|
||||
self._style.removeProperty(key)
|
||||
|
||||
def set(self, **kwargs):
|
||||
"""Set one or more CSS properties on the element."""
|
||||
for key, value in kwargs.items():
|
||||
self._element._dom_element.style.setProperty(key, value)
|
||||
|
||||
@@ -402,10 +423,188 @@ class ContainerElement(Element):
|
||||
else:
|
||||
self.innerHTML += child
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.children
|
||||
|
||||
# Classes for every element type. If the element type (e.g. "input") clashes with
|
||||
|
||||
class ClassesCollection:
|
||||
"""A set-like interface to the classes of the elements in a collection."""
|
||||
|
||||
def __init__(self, collection):
|
||||
self._collection = collection
|
||||
|
||||
def __contains__(self, class_name):
|
||||
for element in self._collection:
|
||||
if class_name in element.classes:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, ClassesCollection)
|
||||
and self._collection == other._collection
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
for class_name in self._all_class_names():
|
||||
yield class_name
|
||||
|
||||
def __len__(self):
|
||||
return len(self._all_class_names())
|
||||
|
||||
def __repr__(self):
|
||||
return f"ClassesCollection({repr(self._collection)})"
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(self._all_class_names())
|
||||
|
||||
def add(self, *class_names):
|
||||
"""Add one or more classes to the elements in the collection."""
|
||||
for element in self._collection:
|
||||
element.classes.add(*class_names)
|
||||
|
||||
def contains(self, class_name):
|
||||
"""Check if any element in the collection has the specified class."""
|
||||
return class_name in self
|
||||
|
||||
def remove(self, *class_names):
|
||||
"""Remove one or more classes from the elements in the collection."""
|
||||
|
||||
for element in self._collection:
|
||||
element.classes.remove(*class_names)
|
||||
|
||||
def replace(self, old_class, new_class):
|
||||
"""Replace one of the classes in the elements in the collection with another."""
|
||||
for element in self._collection:
|
||||
element.classes.replace(old_class, new_class)
|
||||
|
||||
def toggle(self, *class_names):
|
||||
"""Toggle one or more classes on the elements in the collection."""
|
||||
for element in self._collection:
|
||||
element.classes.toggle(*class_names)
|
||||
|
||||
def _all_class_names(self):
|
||||
all_class_names = set()
|
||||
for element in self._collection:
|
||||
for class_name in element.classes:
|
||||
all_class_names.add(class_name)
|
||||
|
||||
return all_class_names
|
||||
|
||||
|
||||
class StyleCollection:
|
||||
"""A dict-like interface to the styles of the elements in a collection."""
|
||||
|
||||
def __init__(self, collection):
|
||||
self._collection = collection
|
||||
|
||||
def __getitem__(self, key):
|
||||
return [element.style[key] for element in self._collection._elements]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
for element in self._collection._elements:
|
||||
element.style[key] = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"StyleCollection({repr(self._collection)})"
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove a CSS property from the elements in the collection."""
|
||||
for element in self._collection._elements:
|
||||
element.style.remove(key)
|
||||
|
||||
|
||||
class ElementCollection:
|
||||
@classmethod
|
||||
def wrap_dom_elements(cls, dom_elements):
|
||||
"""Wrap an iterable of dom_elements in an `ElementCollection`."""
|
||||
|
||||
return cls(
|
||||
[Element.wrap_dom_element(dom_element) for dom_element in dom_elements]
|
||||
)
|
||||
|
||||
def __init__(self, elements: [Element]):
|
||||
self._elements = elements
|
||||
self._classes = ClassesCollection(self)
|
||||
self._style = StyleCollection(self)
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check for equality by comparing the underlying DOM elements."""
|
||||
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get an item in the collection.
|
||||
|
||||
If `key` is an integer or a slice we use it to index/slice the collection.
|
||||
Otherwise, we use `key` as a query selector.
|
||||
"""
|
||||
if isinstance(key, int):
|
||||
return self._elements[key]
|
||||
|
||||
elif isinstance(key, slice):
|
||||
return ElementCollection(self._elements[key])
|
||||
|
||||
return self.find(key)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._elements
|
||||
|
||||
def __len__(self):
|
||||
return len(self._elements)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{self.__class__.__name__} (length: {len(self._elements)}) "
|
||||
f"{self._elements}"
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return [getattr(element, name) for element in self._elements]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# This class overrides `__setattr__` to delegate "public" attributes to the
|
||||
# elements in the collection. BUT, we don't use the usual Python pattern where
|
||||
# we set attributes on the collection itself via `self.__dict__` as that is not
|
||||
# yet supported in our build of MicroPython. Instead, we handle it here by
|
||||
# using super for all "private" attributes (those starting with an underscore).
|
||||
if name.startswith("_"):
|
||||
super().__setattr__(name, value)
|
||||
|
||||
else:
|
||||
for element in self._elements:
|
||||
setattr(element, name, value)
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
"""Return the classes of the elements in the collection as a `ClassesCollection`."""
|
||||
return self._classes
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
"""Return the elements in the collection as a list."""
|
||||
return self._elements
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
""""""
|
||||
return self._style
|
||||
|
||||
def find(self, selector):
|
||||
"""Find all elements that match the specified selector.
|
||||
|
||||
Return the results as a (possibly empty) `ElementCollection`.
|
||||
"""
|
||||
elements = []
|
||||
for element in self._elements:
|
||||
elements.extend(element.find(selector))
|
||||
|
||||
return ElementCollection(elements)
|
||||
|
||||
|
||||
# Classes for every HTML element. If the element tag name (e.g. "input") clashes with
|
||||
# either a Python keyword or common symbol, then we suffix the class name with an "_"
|
||||
# (e.g. "input_").
|
||||
# (e.g. the class for the "input" element is "input_").
|
||||
|
||||
|
||||
class a(ContainerElement):
|
||||
@@ -463,7 +662,7 @@ class button(ContainerElement):
|
||||
class canvas(ContainerElement):
|
||||
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
|
||||
|
||||
def download(self, filename: str = "snapped.png") -> None:
|
||||
def download(self, filename: str = "snapped.png"):
|
||||
"""Download the current element with the filename provided in input.
|
||||
|
||||
Inputs:
|
||||
@@ -905,167 +1104,6 @@ class wbr(Element):
|
||||
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr"""
|
||||
|
||||
|
||||
class ClassesCollection:
|
||||
def __init__(self, collection: "ElementCollection") -> None:
|
||||
self._collection = collection
|
||||
|
||||
def __contains__(self, class_name):
|
||||
for element in self._collection:
|
||||
if class_name in element.classes:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, ClassesCollection)
|
||||
and self._collection == other._collection
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
for class_name in self._all_class_names():
|
||||
yield class_name
|
||||
|
||||
def __len__(self):
|
||||
return len(self._all_class_names())
|
||||
|
||||
def __repr__(self):
|
||||
return f"ClassesCollection({repr(self._collection)})"
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(self._all_class_names())
|
||||
|
||||
def add(self, *class_names):
|
||||
for element in self._collection:
|
||||
element.classes.add(*class_names)
|
||||
|
||||
def contains(self, class_name):
|
||||
return class_name in self
|
||||
|
||||
def remove(self, *class_names):
|
||||
for element in self._collection:
|
||||
element.classes.remove(*class_names)
|
||||
|
||||
def replace(self, old_class, new_class):
|
||||
for element in self._collection:
|
||||
element.classes.replace(old_class, new_class)
|
||||
|
||||
def toggle(self, *class_names):
|
||||
for element in self._collection:
|
||||
element.classes.toggle(*class_names)
|
||||
|
||||
def _all_class_names(self):
|
||||
all_class_names = set()
|
||||
for element in self._collection:
|
||||
for class_name in element.classes:
|
||||
all_class_names.add(class_name)
|
||||
|
||||
return all_class_names
|
||||
|
||||
|
||||
class StyleCollection:
|
||||
def __init__(self, collection: "ElementCollection") -> None:
|
||||
self._collection = collection
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
return obj._get_attribute("style")
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._collection._get_attribute("style")[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
for element in self._collection._elements:
|
||||
element.style[key] = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"StyleCollection({repr(self._collection)})"
|
||||
|
||||
def remove(self, key):
|
||||
for element in self._collection._elements:
|
||||
element.style.remove(key)
|
||||
|
||||
|
||||
class ElementCollection:
|
||||
def __init__(self, elements: [Element]) -> None:
|
||||
self._elements = elements
|
||||
self._classes = ClassesCollection(self)
|
||||
self._style = StyleCollection(self)
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check for equality by comparing the underlying DOM elements."""
|
||||
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
||||
|
||||
def __getitem__(self, key):
|
||||
# If it's an integer we use it to access the elements in the collection
|
||||
if isinstance(key, int):
|
||||
return self._elements[key]
|
||||
|
||||
# If it's a slice we use it to support slice operations over the elements
|
||||
# in the collection
|
||||
elif isinstance(key, slice):
|
||||
return ElementCollection(self._elements[key])
|
||||
|
||||
# If it's anything else (basically a string) we use it as a query selector.
|
||||
return self.find(key)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._elements
|
||||
|
||||
def __len__(self):
|
||||
return len(self._elements)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{self.__class__.__name__} (length: {len(self._elements)}) "
|
||||
f"{self._elements}"
|
||||
)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._get_attribute(item)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
# This class overrides `__setattr__` to delegate "public" attributes to the
|
||||
# elements in the collection. BUT, we don't use the usual Python pattern where
|
||||
# we set attributes on the collection itself via `self.__dict__` as that is not
|
||||
# yet supported in our build of MicroPython. Instead, we handle it here by
|
||||
# using super for all "private" attributes (those starting with an underscore).
|
||||
if key.startswith("_"):
|
||||
super().__setattr__(key, value)
|
||||
|
||||
else:
|
||||
self._set_attribute(key, value)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self._elements
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self._classes
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
return self._style
|
||||
|
||||
def find(self, selector):
|
||||
elements = []
|
||||
for element in self._elements:
|
||||
elements.extend(element.find(selector))
|
||||
|
||||
return ElementCollection(elements)
|
||||
|
||||
def _get_attribute(self, attr, index=None):
|
||||
if index is None:
|
||||
return [getattr(el, attr) for el in self._elements]
|
||||
|
||||
# As JQuery, when getting an attr, only return it for the first element
|
||||
return getattr(self._elements[index], attr)
|
||||
|
||||
def _set_attribute(self, attr, value):
|
||||
for el in self._elements:
|
||||
setattr(el, attr, value)
|
||||
|
||||
|
||||
# fmt: off
|
||||
ELEMENT_CLASSES = [
|
||||
a, abbr, address, area, article, aside, audio,
|
||||
@@ -1092,7 +1130,47 @@ ELEMENT_CLASSES = [
|
||||
# fmt: on
|
||||
|
||||
|
||||
# Lookup table to get an element class by its tag name.
|
||||
ELEMENT_CLASSES_BY_TAG_NAME = {
|
||||
cls.__name__.replace("_", ""): cls for cls in ELEMENT_CLASSES
|
||||
}
|
||||
# Register all the default (aka "built-in") Element classes.
|
||||
Element.register_element_classes(ELEMENT_CLASSES)
|
||||
|
||||
|
||||
class Page:
|
||||
"""Represents the whole page."""
|
||||
|
||||
def __init__(self):
|
||||
self.html = Element.wrap_dom_element(document.documentElement)
|
||||
self.body = Element.wrap_dom_element(document.body)
|
||||
self.head = Element.wrap_dom_element(document.head)
|
||||
|
||||
def __getitem__(self, selector):
|
||||
"""Get an item on the page.
|
||||
|
||||
We don't index/slice the page like we do with `Element` and `ElementCollection`
|
||||
as it is a bit muddier what the ideal behavior should be. Instead, we simply
|
||||
use this as a convenience method to `find` elements on the page.
|
||||
"""
|
||||
return self.find(selector)
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""Return the page title."""
|
||||
return document.title
|
||||
|
||||
@title.setter
|
||||
def title(self, value):
|
||||
"""Set the page title."""
|
||||
document.title = value
|
||||
|
||||
def append(self, *items):
|
||||
"""Shortcut for `page.body.append`."""
|
||||
self.body.append(*items)
|
||||
|
||||
def find(self, selector): # NOQA
|
||||
"""Find all elements that match the specified selector.
|
||||
|
||||
Return the results as a (possibly empty) `ElementCollection`.
|
||||
"""
|
||||
return ElementCollection.wrap_dom_elements(document.querySelectorAll(selector))
|
||||
|
||||
|
||||
page = Page()
|
||||
@@ -1,22 +0,0 @@
|
||||
from pyscript import document
|
||||
from pyscript.web.elements import Element, ElementCollection
|
||||
|
||||
|
||||
class DOM:
|
||||
def __init__(self):
|
||||
self.body = Element.from_dom_element(document.body)
|
||||
self.head = Element.from_dom_element(document.head)
|
||||
|
||||
def __getitem__(self, selector):
|
||||
return self.find(selector)
|
||||
|
||||
def find(self, selector):
|
||||
return ElementCollection(
|
||||
[
|
||||
Element.from_dom_element(dom_element)
|
||||
for dom_element in document.querySelectorAll(selector)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
dom = DOM()
|
||||
@@ -1,13 +1,11 @@
|
||||
from pyscript import document, when
|
||||
from pyscript.web import dom
|
||||
from pyscript.web import elements as el
|
||||
from pyscript.web.elements import ElementCollection
|
||||
from pyscript.web import Element, ElementCollection, div, p, page
|
||||
|
||||
|
||||
class TestDocument:
|
||||
def test__element(self):
|
||||
assert dom.body._dom_element == document.body
|
||||
assert dom.head._dom_element == document.head
|
||||
assert page.body._dom_element == document.body
|
||||
assert page.head._dom_element == document.head
|
||||
|
||||
|
||||
def test_getitem_by_id():
|
||||
@@ -16,13 +14,13 @@ def test_getitem_by_id():
|
||||
txt = "You found test_id_selector"
|
||||
selector = f"#{id_}"
|
||||
# EXPECT the element to be found by id
|
||||
result = dom.find(selector)
|
||||
result = page.find(selector)
|
||||
div = result[0]
|
||||
# EXPECT the element text value to match what we expect and what
|
||||
# the JS document.querySelector API would return
|
||||
assert document.querySelector(selector).innerHTML == div.innerHTML == txt
|
||||
# EXPECT the results to be of the right types
|
||||
assert isinstance(div, el.Element)
|
||||
assert isinstance(div, Element)
|
||||
assert isinstance(result, ElementCollection)
|
||||
|
||||
|
||||
@@ -33,8 +31,7 @@ def test_getitem_by_class():
|
||||
"test_selector_w_children_child_1",
|
||||
]
|
||||
expected_class = "a-test-class"
|
||||
result = dom.find(f".{expected_class}")
|
||||
div = result[0]
|
||||
result = page.find(f".{expected_class}")
|
||||
|
||||
# EXPECT to find exact number of elements with the class in the page (== 3)
|
||||
assert len(result) == 3
|
||||
@@ -44,7 +41,7 @@ def test_getitem_by_class():
|
||||
|
||||
|
||||
def test_read_n_write_collection_elements():
|
||||
elements = dom.find(".multi-elems")
|
||||
elements = page.find(".multi-elems")
|
||||
|
||||
for element in elements:
|
||||
assert element.innerHTML == f"Content {element.id.replace('#', '')}"
|
||||
@@ -59,15 +56,15 @@ class TestElement:
|
||||
def test_query(self):
|
||||
# GIVEN an existing element on the page, with at least 1 child element
|
||||
id_ = "test_selector_w_children"
|
||||
parent_div = dom.find(f"#{id_}")[0]
|
||||
parent_div = page.find(f"#{id_}")[0]
|
||||
|
||||
# EXPECT it to be able to query for the first child element
|
||||
div = parent_div.find("div")[0]
|
||||
|
||||
# EXPECT the new element to be associated with the parent
|
||||
assert div.parent == parent_div
|
||||
# EXPECT the new element to be a el.Element
|
||||
assert isinstance(div, el.Element)
|
||||
# EXPECT the new element to be an Element
|
||||
assert isinstance(div, Element)
|
||||
# EXPECT the div attributes to be == to how they are configured in the page
|
||||
assert div.innerHTML == "Child 1"
|
||||
assert div.id == "test_selector_w_children_child_1"
|
||||
@@ -76,8 +73,8 @@ class TestElement:
|
||||
# GIVEN 2 different Elements pointing to the same underlying element
|
||||
id_ = "test_id_selector"
|
||||
selector = f"#{id_}"
|
||||
div = dom.find(selector)[0]
|
||||
div2 = dom.find(selector)[0]
|
||||
div = page.find(selector)[0]
|
||||
div2 = page.find(selector)[0]
|
||||
|
||||
# EXPECT them to be equal
|
||||
assert div == div2
|
||||
@@ -92,27 +89,27 @@ class TestElement:
|
||||
|
||||
def test_append_element(self):
|
||||
id_ = "element-append-tests"
|
||||
div = dom.find(f"#{id_}")[0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
new_el = el.p("new element")
|
||||
new_el = p("new element")
|
||||
div.append(new_el)
|
||||
assert len(div.children) == len_children_before + 1
|
||||
assert div.children[-1] == new_el
|
||||
|
||||
def test_append_dom_element_element(self):
|
||||
id_ = "element-append-tests"
|
||||
div = dom.find(f"#{id_}")[0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
new_el = el.p("new element")
|
||||
new_el = p("new element")
|
||||
div.append(new_el._dom_element)
|
||||
assert len(div.children) == len_children_before + 1
|
||||
assert div.children[-1] == new_el
|
||||
|
||||
def test_append_collection(self):
|
||||
id_ = "element-append-tests"
|
||||
div = dom.find(f"#{id_}")[0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
collection = dom.find(".collection")
|
||||
collection = page.find(".collection")
|
||||
div.append(collection)
|
||||
assert len(div.children) == len_children_before + len(collection)
|
||||
|
||||
@@ -122,16 +119,16 @@ class TestElement:
|
||||
def test_read_classes(self):
|
||||
id_ = "test_class_selector"
|
||||
expected_class = "a-test-class"
|
||||
div = dom.find(f"#{id_}")[0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
assert div.classes == [expected_class]
|
||||
|
||||
def test_add_remove_class(self):
|
||||
id_ = "div-no-classes"
|
||||
classname = "tester-class"
|
||||
div = dom.find(f"#{id_}")[0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
assert not div.classes
|
||||
div.classes.add(classname)
|
||||
same_div = dom.find(f"#{id_}")[0]
|
||||
same_div = page.find(f"#{id_}")[0]
|
||||
assert div.classes == [classname] == same_div.classes
|
||||
div.classes.remove(classname)
|
||||
assert div.classes == [] == same_div.classes
|
||||
@@ -139,7 +136,7 @@ class TestElement:
|
||||
def test_when_decorator(self):
|
||||
called = False
|
||||
|
||||
just_a_button = dom.find("#a-test-button")[0]
|
||||
just_a_button = page.find("#a-test-button")[0]
|
||||
|
||||
@when("click", just_a_button)
|
||||
def on_click(event):
|
||||
@@ -155,7 +152,7 @@ class TestElement:
|
||||
|
||||
def test_inner_html_attribute(self):
|
||||
# GIVEN an existing element on the page with a known empty text content
|
||||
div = dom.find("#element_attribute_tests")[0]
|
||||
div = page.find("#element_attribute_tests")[0]
|
||||
|
||||
# WHEN we set the html attribute
|
||||
div.innerHTML = "<b>New Content</b>"
|
||||
@@ -167,7 +164,7 @@ class TestElement:
|
||||
|
||||
def test_text_attribute(self):
|
||||
# GIVEN an existing element on the page with a known empty text content
|
||||
div = dom.find("#element_attribute_tests")[0]
|
||||
div = page.find("#element_attribute_tests")[0]
|
||||
|
||||
# WHEN we set the html attribute
|
||||
div.textContent = "<b>New Content</b>"
|
||||
@@ -184,12 +181,12 @@ class TestElement:
|
||||
|
||||
class TestCollection:
|
||||
def test_iter_eq_children(self):
|
||||
elements = dom.find(".multi-elems")
|
||||
assert [el for el in elements] == [el for el in elements.children]
|
||||
elements = page.find(".multi-elems")
|
||||
assert [el for el in elements] == [el for el in elements.elements]
|
||||
assert len(elements) == 3
|
||||
|
||||
def test_slices(self):
|
||||
elements = dom.find(".multi-elems")
|
||||
elements = page.find(".multi-elems")
|
||||
assert elements[0]
|
||||
_slice = elements[:2]
|
||||
assert len(_slice) == 2
|
||||
@@ -199,26 +196,26 @@ class TestCollection:
|
||||
|
||||
def test_style_rule(self):
|
||||
selector = ".multi-elems"
|
||||
elements = dom.find(selector)
|
||||
elements = page.find(selector)
|
||||
for el in elements:
|
||||
assert el.style["background-color"] != "red"
|
||||
|
||||
elements.style["background-color"] = "red"
|
||||
|
||||
for i, el in enumerate(dom.find(selector)):
|
||||
for i, el in enumerate(page.find(selector)):
|
||||
assert elements[i].style["background-color"] == "red"
|
||||
assert el.style["background-color"] == "red"
|
||||
|
||||
elements.style.remove("background-color")
|
||||
|
||||
for i, el in enumerate(dom.find(selector)):
|
||||
for i, el in enumerate(page.find(selector)):
|
||||
assert el.style["background-color"] != "red"
|
||||
assert elements[i].style["background-color"] != "red"
|
||||
|
||||
def test_when_decorator(self):
|
||||
called = False
|
||||
|
||||
buttons_collection = dom.find("button")
|
||||
buttons_collection = page.find("button")
|
||||
|
||||
@when("click", buttons_collection)
|
||||
def on_click(event):
|
||||
@@ -236,34 +233,33 @@ class TestCollection:
|
||||
|
||||
class TestCreation:
|
||||
def test_create_document_element(self):
|
||||
# TODO: This test should probably be removed since it's testing the elements module
|
||||
new_el = el.div("new element")
|
||||
# TODO: This test should probably be removed since it's testing the elements
|
||||
# module.
|
||||
new_el = div("new element")
|
||||
new_el.id = "new_el_id"
|
||||
assert isinstance(new_el, el.Element)
|
||||
assert isinstance(new_el, Element)
|
||||
assert new_el._dom_element.tagName == "DIV"
|
||||
# EXPECT the new element to be associated with the document
|
||||
assert new_el.parent == None
|
||||
dom.body.append(new_el)
|
||||
assert new_el.parent is None
|
||||
page.body.append(new_el)
|
||||
|
||||
assert dom.find("#new_el_id")[0].parent == dom.body
|
||||
assert page.find("#new_el_id")[0].parent == page.body
|
||||
|
||||
def test_create_element_child(self):
|
||||
selector = "#element-creation-test"
|
||||
parent_div = dom.find(selector)[0]
|
||||
parent_div = page.find(selector)[0]
|
||||
|
||||
# Creating an element from another element automatically creates that element
|
||||
# as a child of the original element
|
||||
new_el = el.p(
|
||||
"a div", classes=["code-description"], innerHTML="Ciao PyScripters!"
|
||||
)
|
||||
new_el = p("a div", classes=["code-description"], innerHTML="Ciao PyScripters!")
|
||||
parent_div.append(new_el)
|
||||
|
||||
assert isinstance(new_el, el.Element)
|
||||
assert isinstance(new_el, Element)
|
||||
assert new_el._dom_element.tagName == "P"
|
||||
|
||||
# EXPECT the new element to be associated with the document
|
||||
assert new_el.parent == parent_div
|
||||
assert dom.find(selector)[0].children[0] == new_el
|
||||
assert page.find(selector)[0].children[0] == new_el
|
||||
|
||||
|
||||
class TestInput:
|
||||
@@ -277,7 +273,7 @@ class TestInput:
|
||||
def test_value(self):
|
||||
for id_ in self.input_ids:
|
||||
expected_type = id_.split("_")[-1]
|
||||
result = dom.find(f"#{id_}")
|
||||
result = page.find(f"#{id_}")
|
||||
input_el = result[0]
|
||||
assert input_el._dom_element.type == expected_type
|
||||
assert input_el.value == f"Content {id_}" == input_el._dom_element.value
|
||||
@@ -295,7 +291,7 @@ class TestInput:
|
||||
|
||||
def test_set_value_collection(self):
|
||||
for id_ in self.input_ids:
|
||||
input_el = dom.find(f"#{id_}")
|
||||
input_el = page.find(f"#{id_}")
|
||||
|
||||
assert input_el.value[0] == f"Content {id_}" == input_el[0].value
|
||||
|
||||
@@ -308,30 +304,30 @@ class TestInput:
|
||||
# actually on the class. Maybe a job for __setattr__?
|
||||
#
|
||||
# def test_element_without_value(self):
|
||||
# result = dom.find(f"#tests-terminal"][0]
|
||||
# result = page.find(f"#tests-terminal"][0]
|
||||
# with pytest.raises(AttributeError):
|
||||
# result.value = "some value"
|
||||
#
|
||||
# def test_element_without_value_via_collection(self):
|
||||
# result = dom.find(f"#tests-terminal"]
|
||||
# result = page.find(f"#tests-terminal"]
|
||||
# with pytest.raises(AttributeError):
|
||||
# result.value = "some value"
|
||||
|
||||
|
||||
class TestSelect:
|
||||
def test_select_options_iter(self):
|
||||
select = dom.find(f"#test_select_element_w_options")[0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
|
||||
for i, option in enumerate(select.options, 1):
|
||||
assert option.value == f"{i}"
|
||||
assert option.innerHTML == f"Option {i}"
|
||||
|
||||
def test_select_options_len(self):
|
||||
select = dom.find(f"#test_select_element_w_options")[0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
assert len(select.options) == 2
|
||||
|
||||
def test_select_options_clear(self):
|
||||
select = dom.find(f"#test_select_element_to_clear")[0]
|
||||
select = page.find(f"#test_select_element_to_clear")[0]
|
||||
assert len(select.options) == 3
|
||||
|
||||
select.options.clear()
|
||||
@@ -340,7 +336,7 @@ class TestSelect:
|
||||
|
||||
def test_select_element_add(self):
|
||||
# GIVEN the existing select element with no options
|
||||
select = dom.find(f"#test_select_element")[0]
|
||||
select = page.find(f"#test_select_element")[0]
|
||||
|
||||
# EXPECT the select element to have no options
|
||||
assert len(select.options) == 0
|
||||
@@ -431,7 +427,7 @@ class TestSelect:
|
||||
|
||||
def test_select_options_remove(self):
|
||||
# GIVEN the existing select element with 3 options
|
||||
select = dom.find(f"#test_select_element_to_remove")[0]
|
||||
select = page.find(f"#test_select_element_to_remove")[0]
|
||||
|
||||
# EXPECT the select element to have 3 options
|
||||
assert len(select.options) == 4
|
||||
@@ -453,7 +449,7 @@ class TestSelect:
|
||||
|
||||
def test_select_get_selected_option(self):
|
||||
# GIVEN the existing select element with one selected option
|
||||
select = dom.find(f"#test_select_element_w_options")[0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
|
||||
# WHEN we get the selected option
|
||||
selected_option = select.options.selected
|
||||
|
||||
@@ -101,11 +101,9 @@ class TestElements(PyScriptTest):
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript.web import dom
|
||||
from pyscript.web.elements import {el_type}
|
||||
|
||||
from pyscript.web import page, {el_type}
|
||||
el = {el_type}({attributes})
|
||||
dom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
@@ -620,13 +618,12 @@ class TestElements(PyScriptTest):
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript.web import dom
|
||||
from pyscript.web.elements import div, p
|
||||
from pyscript.web import page, div, p
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child = p('{p_text_content}')
|
||||
el.append(child)
|
||||
dom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
@@ -664,14 +661,13 @@ class TestElements(PyScriptTest):
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript import document
|
||||
from pyscript.web import dom
|
||||
from pyscript.web.elements import div, p
|
||||
from pyscript.web import page, div, p
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child = document.createElement('P')
|
||||
child.textContent = '{p_text_content}'
|
||||
el.append(child)
|
||||
dom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
@@ -709,15 +705,14 @@ class TestElements(PyScriptTest):
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript.web import dom
|
||||
from pyscript.web.elements import div, p, ElementCollection
|
||||
from pyscript.web import page, div, p, ElementCollection
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child1 = p('{p_text_content}')
|
||||
child2 = p('{p2_text_content}', id='child2')
|
||||
collection = ElementCollection([child1, child2])
|
||||
el.append(collection)
|
||||
dom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
@@ -765,20 +760,19 @@ class TestElements(PyScriptTest):
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript import document
|
||||
from pyscript.web import dom
|
||||
from pyscript.web.elements import div, p, ElementCollection
|
||||
from pyscript.web import page, div, p, ElementCollection
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child1 = p('{p_text_content}')
|
||||
child2 = p('{p2_text_content}', id='child2')
|
||||
|
||||
dom.body.append(child1)
|
||||
dom.body.append(child2)
|
||||
page.body.append(child1)
|
||||
page.body.append(child2)
|
||||
|
||||
nodes = document.querySelectorAll('p')
|
||||
el.append(nodes)
|
||||
|
||||
dom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
|
||||
5
pyscript.core/types/stdlib/pyscript.d.ts
vendored
5
pyscript.core/types/stdlib/pyscript.d.ts
vendored
@@ -9,10 +9,7 @@ declare namespace _default {
|
||||
"magic_js.py": string;
|
||||
"storage.py": string;
|
||||
"util.py": string;
|
||||
web: {
|
||||
"__init__.py": string;
|
||||
"elements.py": string;
|
||||
};
|
||||
"web.py": string;
|
||||
"websocket.py": string;
|
||||
"workers.py": string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user