Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ui.get to query the element tree #1931

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft

Introduce ui.get to query the element tree #1931

wants to merge 17 commits into from

Conversation

rodja
Copy link
Member

@rodja rodja commented Oct 31, 2023

Originated in #1923 (comment), this PR introduces ui.get to retrieve elements of the current page with a query language inspired by tortoise-orm's filter or BeautifulSoup's find_all. Therefore it also introduces a .tags .keys property on Element with wich the developer can set a list of queryable words.

When complete this PR should allow these kinds of operations:

buttons = ui.get(type=ui.button)
headers = ui.get(key='header')  
action_buttons = ui.get(type=ui.button, key='action')
buttons_with_text = ui.get(type=ui.button, text='Click me')
buttons_witout_text = ui.get(type=ui.button).without(text='Click me')
buttons_within_header = ui.get(type=ui.button).within(key='header')
buttons_within_header_or_footer = ui.get(type=ui.button).within(key=['header', 'footer'])

The object which is returned by ui.get is called Elements (eg. group of elements) and allows setting props,classes,style on all containing elements at once:

ui.get(key='important').classes('text-bold')

And it allows iterating over all elements:

for element in ui.get(key='important'):
    print(element)

If type= is used the iterator automatically casts to this type:

for stepper in ui.get(type=ui.stepper):
    stepper.next()

ToDos

  • type=
  • iterating with typing
  • .classes
  • .style
  • .props
  • text=
  • key= and .keys
  • .within(type=
  • .within(key=
  • .exclude(type=
  • .exclude(key=
  • .exclude(text=
  • .not_within(type=
  • .not_within(key=
  • .key with string or List
  • .type with class or List simplify usage of Mixins
  • .text with string or List
  • docstrings
  • documentation_get.py with demos
  • looking through the examples and checking where ui.get would make sense
  • discuss general impact on maintenance etc.

@rodja rodja added the enhancement New feature or request label Oct 31, 2023
@ed2050
Copy link

ed2050 commented Oct 31, 2023

Thanks @rodja, this looks like a great start.

Questions

  1. What are tags in this system? Is it a new free-form id as in Class selectors or query language to find elements #1923? Users can set to any value they want? What are the identifier rules, \w+?
  2. Can elements have just one tag or several?
  3. Is there a way to update tags after element creation? I find this to be a general issue across nicegui. Parameters like icon= seem to be set-and-forget with no apparent way to change them later (or maybe I missed it).
  4. The Elements return value: when it iterates over elements, do we get the actual element or a proxy? I want to access more than just styles / props / classes. I want full access to the element so I can call move(), disable(), update source=, etc. If it's a proxy, then it should at least contain a reference to the underlying object. Like:
for proxy in ui.get (type = 'ui.button') :
    proxy.classes ('text-bold')
    element = proxy.element   # reference to full element
  1. Not sure I understand the stepper example. What are you saying about type= iterators?

Other comments

I'll also point out that callling them tags can be a bit confusing, since elements ultimately render to html tags. Maybe the parameter could be called ntags (niceguitags) or utags (user tags) or otags (object tags) or something to better distinguish. I would suggest labels but you already have ui.label.

I know you don't want users to have to know html to use nicegui, and that's fine. But a large portion of programmers do know html. Any serious nicegui users will eventually run into html & css issues. Avoiding html terms is helpful to reduce confusion. If you don't know html then it doesn't matter what you call it, labels or tags or names work equally well.

Hope this helps. Looking forward to this development! 😃

@rodja
Copy link
Member Author

rodja commented Oct 31, 2023

Thanks for the comments and questions @ed2050.

You are right. The name "tags" is not optimal. We need to find something else. Ideally it should not be cryptical like ntags or so. Maybe "markers"?

Questions 1-3: The idea is that it works like classes. You can define multiple and change them on the fly. But they are not interpreted by the browser and hence can be used without clashing with existing quasar/tailwind classes.

Question 4-6: As you can see in the tests you have the "real" NiceGUI elements, when iterating over the Elements group (and the items have the correct type hints, if you use the type= parameter):

result = ', '.join(b.text for b in ui.get(type=ui.button))
.

@ed2050
Copy link

ed2050 commented Oct 31, 2023

Fantastic. I suspected as much, thanks for verifying.

Possible names:

  • markers
  • marks
  • names
  • flags
  • userids (or uids)

Acually I think labels is the best fit. ui.label doesn't seem to be a particularly descriptive name for a text component; ui.text would be more natural. But I suppose it's too much hassle to change now.

After that my next choice would be marks. Names has possible confusion with html (both tag "names" aka h1,img,body, etc or occasionally name= attributes). Tags is a better fit, unfortunately overlaps with html. Userid suggests single unique values, and flags are usually boolean.

That said, any of the above would work. My preference is for shorter names, but any will do.

Thanks @rodja.

@rodja
Copy link
Member Author

rodja commented Nov 1, 2023

I also like .labels. We already have the ongoing discussion about renaming ui.label into ui.text in #1805.

@rodja
Copy link
Member Author

rodja commented Nov 1, 2023

After some discussion about the naming with ChatGPT 4, it came up with this rating:

  • Attributes: ★★★★☆
  • Keys: ★★★★☆
  • Labels: ★★★★☆
  • Identifiers: ★★★☆☆
  • Tags: ★★★☆☆
  • Marks: ★★★☆☆
  • Markers: ★★★☆☆
  • Categories: ★★☆☆☆
  • Flags: ★★☆☆☆
  • Traits: ★★☆☆☆
  • Descriptors: ★★☆☆☆
  • Metadata: ★★☆☆☆
  • Names: ★★☆☆☆
  • Userids (or uids): ★☆☆☆☆

I'll go with .keys for now because its short and does not clash with existing ui.label. Also I want us to get a feel on how problematic the term overlap with "keys" in a Python dict is.

@rodja
Copy link
Member Author

rodja commented Nov 1, 2023

When I started implementing ui.get(type=[ui.button, ui.text]) I realized that this is probably not a good idea because we would loose typing in the iterator. Then I discovered that this is already working:

    from nicegui.elements.mixins.text_element import TextElement

    ui.button('button A')
    ui.label('label A')
    ui.icon('home')
    ui.button('button B')
    ui.label('label B')

    result = [b.text for b in ui.get(type=TextElement)]

So we only need to think if we want to make the mixins better acessable or not.

@rodja rodja added this to the Later milestone Nov 6, 2023
@rodja
Copy link
Member Author

rodja commented Nov 26, 2023

I think

drawers = [element for element in context.get_client().elements.values() if isinstance(element, ui.left_drawer)]

from the documentation structure rewrite in #2084 is a good example where ui.get would simplify things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants