Code Monkey home page Code Monkey logo

Comments (4)

4l1fe avatar 4l1fe commented on May 22, 2024 1

Well, i pretty understand avoidance of verbosity, var names cluttering and building UIs through ui object only. My point was actually more about pythonic explicit way of coding PEP20

>>> import this
The Zen of Python, by Tim Peters

...
Explicit is better than implicit.
...

I think if you mention the concept with a few details in the documentation(your explanation above, for example) it would keep people away of being a bit confused on it as they can follow the idea. I mostly mean a python developers category.

from nicegui.

falkoschindler avatar falkoschindler commented on May 22, 2024

Hi, thanks for reporting this issue! I think I can explain what is going on an how to fix your example.

By default NiceGUI starts a uvicorn server with auto-reload activated. This already happens during import and that's why the main script (run.py in your case) blocks within from nicegui import ui and never reaches the main guard. The uvicorn reloader takes over and runs your script, but the reloader is not __main__. We chose this approach because we like working with auto-reload very much and wanted to avoid evaluating the main script twice, once with the server and once with the auto-reload mechanism.

You can either remove the main guard or at least move ui = initialize() one line up. Or you disable auto-reload with ui.run(reload=False). Then the server is only started during ui.run and the control flow is as expected.

With some clean up here and there, this is your resulting code:

repr_layer.py

from nicegui import ui

def add_elements(container):
    with container:
        result = 'something'
        ui.label(result)

def initialize_ui():
    ui.label('Any label')
    row = ui.row()
    ui.button('+', on_click=lambda: add_elements(row))
    return ui

run.py

from repr_layer import initialize_ui

if __name__ == '__main__':
    ui = initialize_ui()
    ui.run(reload=False)

Notes:

  • no need to import ui in run.py
  • no need for container.update() (happens automatically when leaving the container scope)
  • ui.label instead of container.label
  • added '+' (just because why not)

Let me drop a few thoughts about the OOP design decisions you mentioned.

Implicit nesting via scopes

We wanted NiceGUI to be an intuitive API for creating user interfaces. UIs are typically hierarchically organized and markup languages like HTML reflect that visually:

<div id="container">
    <div id="box">
        <p>Hello world!</p>
    </div>
</div>

That's why we wanted a UI description with NiceGUI to have a similar form:

with ui.card():
    with ui.row():
        ui.label('Hello world!')

JustPy, on the other hand, becomes tricky to read when nesting UI elements:

container = Div()
box = Div()
label = Div(text='Hello world!')
box.add(label)
container.add(box)

(Sure, you could create some objects inline. But if you need their references, it clutters quickly.)

Thus, with NiceGUI you don't create hierarchy via parent/children arguments or add/insert methods, but via indentation and scoping. I.e. using a with statement opens a scope and new elements are automatically nested there. This is unusual, but turns out very handy.

Constructors as functions with side-effects

A function like ui.label is actually a constructor of a Label object. But we chose to use lowercase names in order to indicate the function-like behavior. Besides creating an object ui.label, modifies the UI. In many cases, you don't event need the object reference.

Again, compared to an approach like JustPy, we think NiceGUI is more convenient.

Comparison to Streamlit

Streamlit and its weird control flow was actually the motivation for us to write NiceGUI. See #21 for a more detailed discussion.

from nicegui.

4l1fe avatar 4l1fe commented on May 22, 2024

Thank you for the details. I tested both ways out.

Not working

With the reloading disabled a web server isn't getting started.

from repr_layer import initialize_ui

if __name__ == '__main__':
    ui = initialize_ui()
    ui.run(reload=False)

Working

With the main guard removed it works. That's my choice.

Comment on the concept

That's why we wanted a UI description with NiceGUI to have a similar form:

with ui.card():
    with ui.row():
        ui.label('Hello world!')

You know, it's quite intuitive and i like this approach, but let me ask you why do we use ui object in nesting? In my opinion, subelements should be explicitly added to their parent one, label to row in the example above, but not to the root ui element:

with ui.card() as card:
    with card.row() as row:
        row.label('Hello world!')

Now, from an html perspective, i see adding subelements as follow:

<body>  <!-- let's suppose that's `ui` element   -->
  <subelement>  <!-- code line such as `with ui.row(): ui.subelement()` in my mind is placed here and it is NOT correct-->
  <div id="container">
      <div id="box">
          <p>Hello world!</p>
          <subelement>  <!-- would code line `with ui.row() as row: row.subelement()` in your mind places an element here? --> 
      </div>
  </div>
</body>

Anyway, i enjoy Nicegui. Get my comment as a review only πŸ™‚

from nicegui.

falkoschindler avatar falkoschindler commented on May 22, 2024

The "not working" example using reload=False works for me. You might just need to restart the script, since changes to ui.run arguments are ignored during auto-reload. Or maybe there's something else I'm missing. Glad to here that it works without the main guard.


Regarding your comments on our concept: Thanks for the interesting thoughts! It's quiet fun to juggle with the different possibilities a languange like Python gives you to create an API.

Let me elaborate a bit on your example:

with ui.card() as card:
    with card.row() as row:
        row.label('Hello world!')

I see your point. The ui.scene container is actually an example where we followed this approach. In order to separate the list of 3D objects from the "regular" UI elements, they are created like

with ui.scene() as scene:
    scene.sphere()

So introducing a new 3D object label would not conflict with ui.label. But for UI elements we chose to use the short namespace ui. Consider the following example with some more containers:

with ui.card() as main_card:
    with card.row() as info_row:
        info_row.label('Time:')
        info_row.label('CPU%:')
        info_row.label('RAM%:')
    with card.row() as button_row:
        button_row.button('Start')
        button_row.button('Pause')
        button_row.button('Stop')

Repeating the container names is pretty verbose, given the indentation already defines the hierarchy. Sure, you could use names like row1 and row2 or r1 and r2, but this isn't very readable and your namespace get's spoiled quickly. In large UIs you might have to search for free variable names. Or you use the same row for each row, but I'm not sure if this is good practice. And changing the layout (e.g. from row to column) requires you to change row into column many times.

Besides that, your approach would clutter the namespace of, e.g., a card: Is card.color a property or does it create a new UI element of type color? Your IDEs auto-suggestions will become less helpful.

from nicegui.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.