diff --git a/docs/how_it_works.md b/docs/how_it_works.md index e968f03..57b5ef3 100644 --- a/docs/how_it_works.md +++ b/docs/how_it_works.md @@ -30,11 +30,17 @@ The most important JSON object. It contains the following keys: {"type": "page", "func": ..., "args": ..., + "selector-to-element": ..., "url": ..., "html": ..., "uid": ...} ``` -`type` is the type of JSON object, and it has the value 'page' when JavaScript sends the HTML document as data. `func` contains the name of the Python function that should be called, `args` are the arguments of this function, `url` is the URL of the HTML page that sent the data, `html` is the HTML document itself as a string. `uid` is the id of the window when creating desktop apps. +`type` is the type of JSON object, and it has the value 'page' when JavaScript sends the HTML document as data. `func` contains the name of the Python function that should be called, `args` are the arguments of this function, `selector-to-element` is a boolean that is only true if one of the arguments is an HTML element, `url` is the URL of the HTML page that sent the data, `html` is the HTML document itself as a string. `uid` is the id of the window when creating desktop apps. +If one of the argument is an HTML element, it will be converted to a JSON that contains its CSS selector: +```json +{"type": "element", + "selector": ..., +``` #### Files information JSON Another type of JSON object is a JSON that contains uploaded files: diff --git a/docs/index.rst b/docs/index.rst index d94f0be..720e79c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ ToUI ==== -Version: v2.2.0 +Version: v2.3.0 .. include:: ../README.md :parser: myst_parser.sphinx_ diff --git a/docs/toui.elements.Element.get_selector.rst b/docs/toui.elements.Element.get_selector.rst new file mode 100644 index 0000000..86928d6 --- /dev/null +++ b/docs/toui.elements.Element.get_selector.rst @@ -0,0 +1,4 @@ +Element.get_selector +-------------------- + +.. automethod:: toui.elements.Element.get_selector diff --git a/docs/toui.elements.Element.rst b/docs/toui.elements.Element.rst index e84339f..c338fc4 100644 --- a/docs/toui.elements.Element.rst +++ b/docs/toui.elements.Element.rst @@ -22,6 +22,7 @@ Methods toui.elements.Element.get_height_property toui.elements.Element.get_id toui.elements.Element.get_selected + toui.elements.Element.get_selector toui.elements.Element.get_style_property toui.elements.Element.get_value toui.elements.Element.get_width_property diff --git a/docs/toui.elements.IFrameElement.get_selector.rst b/docs/toui.elements.IFrameElement.get_selector.rst new file mode 100644 index 0000000..9f290f9 --- /dev/null +++ b/docs/toui.elements.IFrameElement.get_selector.rst @@ -0,0 +1,4 @@ +IFrameElement.get_selector +-------------------------- + +.. automethod:: toui.elements.IFrameElement.get_selector diff --git a/docs/toui.elements.IFrameElement.rst b/docs/toui.elements.IFrameElement.rst index aae38f2..af04ceb 100644 --- a/docs/toui.elements.IFrameElement.rst +++ b/docs/toui.elements.IFrameElement.rst @@ -22,6 +22,7 @@ Methods toui.elements.IFrameElement.get_height_property toui.elements.IFrameElement.get_id toui.elements.IFrameElement.get_selected + toui.elements.IFrameElement.get_selector toui.elements.IFrameElement.get_style_property toui.elements.IFrameElement.get_value toui.elements.IFrameElement.get_width_property diff --git a/docs/toui.pages.Page.get_element_from_selector.rst b/docs/toui.pages.Page.get_element_from_selector.rst new file mode 100644 index 0000000..366509d --- /dev/null +++ b/docs/toui.pages.Page.get_element_from_selector.rst @@ -0,0 +1,4 @@ +Page.get_element_from_selector +------------------------------ + +.. automethod:: toui.pages.Page.get_element_from_selector diff --git a/docs/toui.pages.Page.rst b/docs/toui.pages.Page.rst index b55caf1..6ee1048 100644 --- a/docs/toui.pages.Page.rst +++ b/docs/toui.pages.Page.rst @@ -16,6 +16,7 @@ Methods toui.pages.Page.from_str toui.pages.Page.get_body_element toui.pages.Page.get_element + toui.pages.Page.get_element_from_selector toui.pages.Page.get_elements toui.pages.Page.get_html_element toui.pages.Page.get_window diff --git a/setup.py b/setup.py index 40b7e0b..28156c3 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ small = False if "--small" in sys.argv: small = True - sys.argv.remove('--small') + sys.argv.remove('--small') name = "ToUI" version = __version__ diff --git a/tests/assets/full_app.html b/tests/assets/full_app.html index 4c61c65..70ea50f 100644 --- a/tests/assets/full_app.html +++ b/tests/assets/full_app.html @@ -37,6 +37,11 @@

All apps

+
+ +

+
+

Desktop apps only


diff --git a/tests/full_app.py b/tests/full_app.py index f057e13..1e70508 100644 --- a/tests/full_app.py +++ b/tests/full_app.py @@ -123,6 +123,16 @@ def change_selected(): pg.get_element("selected").set_content(f"Selected color is {color}") +def get_element_selector(): + pg = app.get_user_page() + element_selector = pg.get_element("get-selector").get_selector() + pg.get_element("display-selector").set_content(element_selector) + + +def get_itself(element): + element.set_content("success") + + def resize(w, h): window = app.get_user_page().get_window() if window: @@ -142,6 +152,8 @@ def resize(w, h): home_page.get_element("display-user-vars").onclick(display_user_vars) home_page.get_element("file-upload").on("change", upload_file) home_page.get_element("colors").on("change", change_selected) +home_page.get_element("get-selector").onclick(get_element_selector) +home_page.get_element("get-itself").onclick(get_itself, return_itself=True) home_page.get_element("resize").onclick(resize, 1000, 1000, quotes=False) diff --git a/toui/__init__.py b/toui/__init__.py index 40df6af..f7d6d5e 100644 --- a/toui/__init__.py +++ b/toui/__init__.py @@ -4,4 +4,4 @@ from .structure import ToUIBlueprint from . import exceptions -__version__ = "v2.2.0" +__version__ = "v2.3.0" diff --git a/toui/_javascript_templates.py b/toui/_javascript_templates.py index 8d03c24..f1551b3 100644 --- a/toui/_javascript_templates.py +++ b/toui/_javascript_templates.py @@ -19,9 +19,20 @@ if (_appType === "DesktopApp" && _pywebviewIsLoaded == false) { await _waitForPywebview() } + + var selector_to_element = false + for (var i = 0; i < args.length; i++) { + if (args[i] instanceof HTMLElement) { + args[i] = {type: "element", + selector: _getElementSelector(args[i])} + selector_to_element = true + } + } + var json = {type: "page", func: func, args: args, + "selector-to-element": selector_to_element, url: urlPath} _manageProperties() json['html'] = _getDoc() @@ -87,6 +98,11 @@ } } + var textarea_elements = document.getElementsByTagName("textarea") + for (var textarea_element of textarea_elements) { + textarea_element.setAttribute("value", textarea_element.value) + } + var select_elements = document.getElementsByTagName("select") for (var select_element of select_elements) { select_element.setAttribute("value", select_element.value) diff --git a/toui/apps.py b/toui/apps.py index 3e02ccf..2feefe2 100644 --- a/toui/apps.py +++ b/toui/apps.py @@ -594,12 +594,22 @@ def _communicate(self, ws): new_page._signal_mode = True new_page._ws = ws new_page._inherit_functions() + selector_to_element = data_dict['selector-to-element'] + if selector_to_element: + for index, arg in enumerate(args): + if type(arg) is dict: + if arg.get('type') == "element": + args[index] = new_page.get_element_from_selector(arg['selector']) if "uid" in data_dict: new_page._uid = data_dict['uid'] session['user page'] = new_page - if new_page._func_exists(func): - new_page._call_func(func, *args) - del session['user page'] + try: + if new_page._func_exists(func): + new_page._call_func(func, *args) + del session['user page'] + except Exception as e: + del session['user page'] + raise e e = time.time() debug(f"TIME: {e - s}s") diff --git a/toui/elements.py b/toui/elements.py index 1454ea4..90749f2 100644 --- a/toui/elements.py +++ b/toui/elements.py @@ -267,6 +267,21 @@ def get_elements(self, tag_name=None, class_name=None, name=None, do_copy=False, "parent": self._selector} elements_list.append(element) return elements_list + + def get_selector(self) -> str: + """ + Gets the CSS selector of an element. + + Returns + ------- + str + + None: + If the element is not part of a page. + + """ + selector = self._element.name + ''.join([f'[{attr}="{value}"]' for attr, value in self._element.attrs.items()]) + return selector def get_attr(self, name): """ @@ -593,7 +608,7 @@ def _manage_content_functions(self, content): else: self._functions[func.__name__] = func - def on(self, event, func_or_name, *func_args, quotes=True): + def on(self, event, func_or_name, *func_args, quotes=True, return_itself=False): """ Creates an HTML event attribute and adds a Python function to it. @@ -621,6 +636,9 @@ def on(self, event, func_or_name, *func_args, quotes=True): quotes: bool, default = True If ``True``, each argument will be surrounded by double quotes. + return_itsef: bool, default=False + If ``True``, the first argument of the function will be the element itself. + Examples -------- @@ -655,6 +673,8 @@ def on(self, event, func_or_name, *func_args, quotes=True): args = ",".join([f'"{arg}"' for arg in func_args]) else: args = ",".join([f'{obj_converter(arg)}' for arg in func_args]) + if return_itself: + args = "this, " + args if callable(func_or_name): name = func_or_name.__name__ if self._parent_page: @@ -666,7 +686,7 @@ def on(self, event, func_or_name, *func_args, quotes=True): value = f"{name}({args})" self.set_attr(name=f"on{event}", value=value) - def onclick(self, func_or_name, *func_args, quotes=True): + def onclick(self, func_or_name, *func_args, quotes=True, return_itself=False): """ Creates the HTML event attribute ``onclick`` and adds a Python function to it. @@ -690,12 +710,15 @@ def onclick(self, func_or_name, *func_args, quotes=True): quotes: bool, default = True If ``True``, each argument will be surrounded by double quotes. + return_itsef: bool, default=False + If ``True``, the first argument of the function will be the element itself. + See Also -------- Element.on """ - self.on('click', func_or_name, *func_args, quotes=quotes) + self.on('click', func_or_name, *func_args, quotes=quotes, return_itself=return_itself) def _from_bs4_tag_no_copy(self, bs4_tag): self._element = bs4_tag diff --git a/toui/pages.py b/toui/pages.py index 42c2523..3253729 100644 --- a/toui/pages.py +++ b/toui/pages.py @@ -338,6 +338,37 @@ def get_elements(self, tag_name=None, class_name=None, name=None, do_copy=False, "number": tag_num} elements_list.append(element) return elements_list + + def get_element_from_selector(self, selector, do_copy=False): + """ + Gets an element from its CSS selector. + + Parameters + ---------- + selector: str + + do_copy: bool, default = False + If ``True``, the element will be copied. + + Returns + ------- + element: Element + If the element was found, an `Element` object will be returned. Otherwise ``None`` + will be returned. + + """ + bs4_tag = self._html.select_one(selector=selector) + if bs4_tag is None: + return None + element = Element() + if do_copy: + element.from_bs4_tag(bs4_tag) + else: + element._from_bs4_tag_no_copy(bs4_tag) + element._parent_page = self + element._signal_mode = self._signal_mode + element._selector = {"selector": selector} + return element def get_html_element(self) -> Element: """