rio icon indicating copy to clipboard operation
rio copied to clipboard

Forms Generation

Open ghost opened this issue 11 months ago • 4 comments

Why don't you add form generation for tables like in django. main feature of battery included.

ghost avatar Jan 28 '25 02:01 ghost

Hello! Can you be more specific please? What API do you have in mind? An example would be nice

mad-moo avatar Jan 28 '25 05:01 mad-moo

Something like this was buried away on one of my lists.

What I had considered was whether the code in the CRUD example could be turned into a function so that one could define the data model (per the MenuItem class) and have the basic form generated automatically, rather than being hard-coded.

This would, for example, mean the options for the Category field that are currently configured at crud_page.py#L150 ("Burger", "Salad", etc.) would be defined along with the field itself in data_models.py using a "choices"-type parameter as with Django (reference). As a result, if the same data model was edited on multiple pages, the valid options would carry across rather than having to be re-coded (e.g. a subset form that only allowed the user to change the category of menu items - a toy example, to be sure, but nonetheless).

Side note: I have continually found the examples very useful. Thanks for writing them!

iwr-redmond avatar Feb 02 '25 18:02 iwr-redmond

We actually have that, and it's shipping with Rio. Thing is, we were never happy with either the API or the results, so it's tucked away in the deepest depths of the package. If you wanna give it a shot:

@dataclasses.dataclass
class MyModel:
    name: str
    enabled: bool


class MyRoot(rio.Component):
    def build(self) -> rio.Component:
        return rio.AutoForm(
            MyModel(
                name="Foo",
                enabled=True,
            ),
            min_width=20,
            align_x=0.5,
            align_y=0.5,
        )

Image

The problem this runs into is that often some fields need more control over the appearance than a dataclass provides. For example, you might want to express a boolean as togglebutton or checkbox instead of switch. It's also difficult to provide validators.

With that said, this issue has given me some new ideas. I'll try making a simple builder class and see how it works out. Something like this:

builder = rio.FormBuilder()
builder.add_text(value=self.bind().text_value, label="Foobar", validate=lambda: ...)
# Keep going to add all items. If an item needs more control, add a custom component:
builder.add_component(MyComponent())

# Then use the builder as component
return builder

What do you think?

mad-moo avatar Feb 02 '25 19:02 mad-moo

In Django/Flask etc, the theme (whether True/False is a checkbox or a switch, etc.) is defined separately to the data model.

I didn't have much luck with pseudo code when we discussed tqdm in #190, but in any case:

## bad pseudocode only ##

class MyDatabase (rio.Database):
    ROLES = {
         "Protagonist",
         "Antagonist",
    }
    def build(self) -> rio.Database:
         # return rio.BlankDatabase(:
         # ...or...
         return rio.PopulatedDatabase(:
             Model(
                name: str,
                role: list(choices = ROLES)
                enabled: bool,
             ),
             Data = [ # I think this is the wrong syntax, but you get the idea :(
               {
                   "name": "Sherlock Holmes",
                   "role": "Protagonist",
                   "enabled": true
               },
               {
                   "name": "Dr John Watson",
                   "role": "Protagonist",
                   "enabled": false
               },
               {
                   "name": "Prof James Moriarty",
                   "role": "Antagonist",
                   "enabled": true
               },
            ]

# define a custom selector type
CustomSelector = rio.checkbox(
    min_width=20,
    align_x=0.5,
    align_y=0.5,
)

# override the default switch with the checkbox
class MyForm(rio.Component):
    def build(self) -> rio.Component:
        return rio.AutoForm(
            Database=MyDatabase,
            Overrides(enabled=CustomSelector)
        )

iwr-redmond avatar Feb 02 '25 19:02 iwr-redmond