Skip to content

Commit

Permalink
zauberzeug#242, zauberzeug#356 review and extend ui.element documenta…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
falkoschindler committed Feb 12, 2023
1 parent 628864b commit 0fd9a27
Showing 1 changed file with 55 additions and 43 deletions.
98 changes: 55 additions & 43 deletions nicegui/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union

from typing_extensions import Self

from . import binding, events, globals, outbox
from .elements.mixins.visibility import Visibility
from .event_listener import EventListener
from .slot import Slot
from typing_extensions import Self

if TYPE_CHECKING:
from .client import Client


PROPS_PATTERN = re.compile(r'([\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)')


class Element(ABC, Visibility):
"""Define the base class for all html elements represented as Python objects.
:param tag: The html tag (e.g. div, p, etc. of the element.)
"""

def __init__(self, tag: str, *, _client: Optional[Client] = None) -> None:
"""Generic Element
This class is also the base class for all other elements.
:param tag: HTML tag of the element
:param _client: client for this element (for internal use only)
"""
super().__init__()
self.client = _client or globals.get_client()
self.id = self.client.next_element_id
Expand All @@ -51,17 +54,21 @@ def __init__(self, tag: str, *, _client: Optional[Client] = None) -> None:
outbox.enqueue_update(self.parent_slot.parent)

def add_slot(self, name: str) -> Slot:
"""Add a slot to the element.
:param name: name of the slot
:return: the slot
"""
self.slots[name] = Slot(self, name)
return self.slots[name]

def __enter__(self) -> Self:
"""Allow element to be used as a context manager (with statement.)"""
self.default_slot.__enter__()
return self

def __exit__(self, *_):
self.default_slot.__exit__(*_)

def _collect_event_dict(self) -> Dict[str, Dict]:
events: Dict[str, Dict] = {}
for listener in self._event_listeners:
Expand Down Expand Up @@ -122,14 +129,13 @@ def classes(self, add: Optional[str] = None, *, remove: Optional[str] = None, re
-> Self:
"""Apply, remove, or replace HTML classes.
Generally, this is for the purpose of modifying the look of the element or its layout, based on the
Quasar framework.
This allows modifying the look of the element or its layout using `Tailwind <https://tailwindcss.com/>`_ or `Quasar <https://quasar.dev/>`_ classes.
.. note:: Removing classes can be helpful if default NiceGUI class-styling is not desired.
Removing or replacing classes can be helpful if predefined classes are not desired.
:param add: a white-space delimited string of classes
:param remove: A white-space delimited string of classes to remove from the element.
:param replace: A white-space delimited string of classes to use instead of existing.
:param add: whitespace-delimited string of classes
:param remove: whitespace-delimited string of classes to remove from the element
:param replace: whitespace-delimited string of classes to use instead of existing ones
"""
class_list = self._classes if replace is None else []
class_list = [c for c in class_list if c not in (remove or '').split()]
Expand All @@ -151,21 +157,18 @@ def _parse_style(text: Optional[str]) -> Dict[str, str]:
result[key.strip()] = value.strip()
return result

def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None) \
-> Self:
"""Apply, remove, or replace CSS style sheet definitions to modify the look of the element.
def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None) -> Self:
"""Apply, remove, or replace CSS definitions.
.. note::
Removing styles can be helpful if the predefined style sheet definitions by NiceGUI are not wanted
in a particular styling.
Removing or replacing styles can be helpful if the predefined style is not desired.
.. codeblock:: python
my_btn=Button("MyButton).style("color: #6E93D6; font-size: 200%", remove="font-weight; background-color")
ui.button('Click me').style('color: #6E93D6; font-size: 200%', remove='font-weight; background-color')
:param add: A semicolon separated list of styles to add to the element.
:param remove: A semicolon separated list of styles to remove from the element.
:param replace: Like add, but existing styles will be replaced by given.
:param add: semicolon-separated list of styles to add to the element
:param remove: semicolon-separated list of styles to remove from the element
:param replace: semicolon-separated list of styles to use instead of existing ones
"""
style_dict = deepcopy(self._style) if replace is None else {}
for key in self._parse_style(remove):
Expand All @@ -190,18 +193,19 @@ def _parse_props(text: Optional[str]) -> Dict[str, Any]:
return dictionary

def props(self, add: Optional[str] = None, *, remove: Optional[str] = None) -> Self:
"""Add or remove Quasar-specif properties to modify the look of the element.
"""Add or remove props.
see https://quasar.dev/vue-components/button#design
This allows modifying the look of the element or its layout using `Quasar <https://quasar.dev/>`_ props.
Since props are simply applied as HTML attributes, they can be used with any HTML element.
.. code:: python
.. codeblock:: python
by_btn = Button("my_btn").props("outline icon=volume_up")
ui.button('Open menu').props('outline icon=menu')
.. note:: Boolean properties are assumed True by their existence.
Boolean properties are assumed ``True`` if no value is specified.
:param add: A whitespace separated list of either boolean values or key=value pair to add
:param remove: A whitespace separated list of property keys to remove.
:param add: whitespace-delimited list of either boolean values or key=value pair to add
:param remove: whitespace-delimited list of property keys to remove
"""
needs_update = False
for key in self._parse_props(remove):
Expand All @@ -217,19 +221,23 @@ def props(self, add: Optional[str] = None, *, remove: Optional[str] = None) -> S
return self

def tooltip(self, text: str) -> Self:
"""Add a tooltip to the element.
:param text: text of the tooltip
"""
with self:
tooltip = Element('q-tooltip')
tooltip._text = text
return self

def on(self, type: str, handler: Optional[Callable], args: Optional[List[str]] = None, *, throttle: float = 0.0) \
-> Self:
"""Subscribe to any web or Quasar events available to an element.
"""Subscribe to an event.
:param type: The name of the event sans the "on" prefix, e.g. "click", "mousedown"
:param handler: The method that is called upon occurrence of the event.
:param args: Additional arguments that should be passed to the event definition.
:param throttle: Force a delay between events to limit frequency or bounce.
:param type: name of the event (without the "on" prefix, e.g. "click" or "mousedown")
:param handler: callback that is called upon occurrence of the event
:param args: arguments included in the event message sent to the event handler (default: `None` meaning all)
:param throttle: minimum time (in seconds) between event occurrences (default: 0.0)
"""
if handler:
args = args if args is not None else ['*']
Expand All @@ -243,10 +251,9 @@ def handle_event(self, msg: Dict) -> None:
events.handle_event(listener.handler, msg, sender=self)

def collect_descendant_ids(self) -> List[int]:
"""
Return a list of ids of the element and each of its descendents.
"""Return a list of IDs of the element and each of its descendants.
.. note:: The first id in the list is that of the element.
The first ID in the list is that of the element itself.
"""
ids: List[int] = [self.id]
for slot in self.slots.values():
Expand All @@ -255,16 +262,22 @@ def collect_descendant_ids(self) -> List[int]:
return ids

def update(self) -> None:
"""Update the element on the client side."""
outbox.enqueue_update(self)

def run_method(self, name: str, *args: Any) -> None:
"""Run a method on the client side.
:param name: name of the method
:param args: arguments to pass to the method
"""
if not globals.loop:
return
data = {'id': self.id, 'name': name, 'args': args}
outbox.enqueue_message('run_method', data, globals._socket_id or self.client.id)

def clear(self) -> None:
"""Remove all descendant (child) elements."""
"""Remove all child elements."""
descendants = [self.client.elements[id] for id in self.collect_descendant_ids()[1:]]
binding.remove(descendants, Element)
for element in descendants:
Expand All @@ -274,10 +287,9 @@ def clear(self) -> None:
self.update()

def remove(self, element: Union[Element, int]) -> None:
"""
Remove a descendant (child) element.
"""Remove a child element.
:param element: Either the element instance or its id.
:param element: either the element instance or its ID
"""
if isinstance(element, int):
children = [child for slot in self.slots.values() for child in slot.children]
Expand Down

0 comments on commit 0fd9a27

Please sign in to comment.