zauberzeug / nicegui

Create web-based user interfaces with Python. The nice way.
https://nicegui.io
MIT License
8.92k stars 540 forks source link

`run_method` and `getElement` return inconsistent types of elements #3019

Open chrschorn opened 4 months ago

chrschorn commented 4 months ago

Description

Hi, originally I wanted to implement a "scroll to element" functionality in an application I'm working on. You can use scrollIntoView() on the DOM node to achieve that. But: how can I get the DOM node for any NiceGUI element?

The issue I ran into:

Here are some examples that work with run_method, then a custom scroll_to I wrote and then ui.input which I couldn't get to work at all:

from nicegui import ui

def scroll_to(element: ui.element):
    ui.run_javascript(f"""
        var el = getElement({element.id});
        console.log(el);
        if (el.$el)
            el = el.$el;
        el.scrollIntoView();
    """)

ui.button("Scroll to label", on_click=lambda: label.run_method("scrollIntoView"))
ui.button("Scroll to button (not working)", on_click=lambda: button.run_method("scrollIntoView"))
ui.button("Scroll to button (works)", on_click=lambda: scroll_to(button))
ui.button("Scroll to input (not working)", on_click=lambda: input.run_method("scrollIntoView"))
ui.button("Scroll to input (not working #2)", on_click=lambda: scroll_to(input))

ui.element().classes("h-[200vh]")  # space for scroll demo

label = ui.label("Far away label")
button = ui.button("Far away button")
input = ui.input("Far away input")

ui.element().classes("h-[200vh]")  # space to make clear which element was scrolled to

ui.run()

Is there already an easy way to get the root DOM node of the NiceGUI element? According to https://nicegui.io/documentation/run_javascript, it should be getElement, but this either returns a DOM node, vue component where $el refers to the root DOM node or, in cases like ui.input, something where even $el points to a non-DOM object.

It would be good to have a reliable way to get the DOM node for an element. Maybe run_method could have a bool argument dom_element? Providing a javascript function documented on run_javascript could also work.

falkoschindler commented 4 months ago

Hi @chrschorn,

That's a great observation. When implementing and extending getElement, we tried to (almost) always return something "useful", but we introduced an annoying inconsistency. We thought, from the user's perspective, it shouldn't matter if a NiceGUI element is based on a Vue component or a plain HTML element. But of course, if you want to do something with it, the difference matters. One could argue that, if you know that an element is based on Vue, you can step into the HTML element using $el, as you did. But this isn't very intuitive, let alone documented.

To support explicit access to Vue components or HTML elements - without breaking the existing API -, we might want to introduce new functions like getHtmlElement and getVueComponent, and deprecate or at least discourage using getElement.

But besides that, what can we do about ui.input? Quasar takes the q-input tag and replaces it with a whole tree of HTML with a <label> as root, but without setting the "id" attribute. I'm not sure what we could do about it, but it would be great to get some kind of consistency with other UI elements.