Multi-part form data binding

I have a form shown in the screenshot below. All of the fields/drop downs are bound to self.item attributes. As the user fills out the form, the follow on panels become visible. If any item isn’t filled in, I want to default 0.00 or “” depending upon the field or raise validation errors/notices for the user.

Questions:

  1. I want to loosely couple the fields to a database row, giving the user the option to cancel or save changes. What’s the best way to do that?

  2. When editing an existing database entry, what’s the best way to load the form with the database row’s values?

  3. Where is the best place to put form field validation logic?

  4. How does self.refresh_data_bindings() work? It seems to clear any of the self.item values that I initialized in init(self, **properties).

Thank you in advance!

Jeff

Congratulations, @jeff! The topic of user data entry, in general, has enough details, if you want them, to become an object of lifetime study and debate.

In this instance, it looks like you still have a number of design decisions ahead. Having concrete criteria can help. (“Best” is not concrete. That is a judgement you can apply once you have criteria.) In fact, at this stage, I’d consider having concrete criteria essential for choosing what’s best.

Some broad suggestions and personal notes… as food for thought.

I find I have maximum flexibility when I decouple the GUI from the DB. That is:

  1. Load the database record into an in-memory dict.
  2. Use data bindings, or not, to connect the dict to the GUI.
  3. On save, write the (edited) dict back to the database.

This gives you the “loose coupling” you were interested in.

For step 2, it is handy to have the dict structured to match the GUI. This is not necessarily a good match to the database structure, however. Steps 1 and 3 can translate between the two kinds of structure.

Validation, where needed, usually occurs somewhere around step 2.5, and is usually tightly integrated with save/cancel/undo/redo logic.

Often, the trick is in deciding how granular you want to make validation/save/cancel/undo/redo. It can be as coarse as an entire data-entry page (all-or-nothing), or as fine-grained as a single entry field, or anything in between.

As a user, I prefer validation to be at the field level. If I’ve made a (catchable) mistake in field 23, I would rather know about it while I’m still at field 23, not 40 fields later.

This is not always practical. When the error is in the relationship between two entered field values (e.g., when two dates must be in a specific order), one cannot say definitively which one is wrong, and one cannot perform a reliable test until both values are known to have been entered. I’d be inclined to perform that test immediately after the 2nd is entered.

Most entry fields have an “on change” event. If you’re validating at the field level, that’s usually a good place to put the validation logic.

As for save/cancel/undo/redo, I tend to think in terms of cohesive objects. If a row or panel’s data entries describe a single, indivisible object (or sub-object), then I would rather have save/cancel/undo/redo on the whole object than on half of it at a time.

1 Like

Hi @p.colbert,

Thank you for the reply! By “best”, I’m looking for the “best practice” or “anvil recommended way.”

I like the approach that you’ve outlined with using a dictionary as the intermediary–that’s generally what I’m trying to achieve. For step 1, where exactly are you storing your dictionary in your form? I’m currently trying to use self.item and binding the relevant fields to the appropriate self.item keys. Can you explain how that approach ties into self.refresh_data_bindings()?

Thanks again!

Jeff

The dictionary can be any member of self. You can continue to call it item, as that will be familiar to other Anvil users. It can contain sub-dictionaries and lists as needed. The browser is aware of item in that, if you do self.item = … the browser will notice, and will attempt to re-populate the on-screen fields.

Thanks to that special awareness, using item would likely count as an Anvil “best practice”.

Where you’re using a RepeatingPanel, the corresponding name would be items (a list of dicts).

The gist of self.refresh_data_bindings() is that it synchronizes the on-screen and in-memory values: the copies that appear in memory (in item/items) are made to be consistent with the copies that appear visually, in the visual components.

But there are nuances of timing. Copying needs to be able to go both ways: memory --> GUI and memory <-- GUI. To get this straight, I’d refer to the documentation, tutorials and many Q&A items (no pun intended) on Anvil’s “Data Binding”. I won’t attempt to reiterate them here. Anvil’s Search features does an excellent job of finding them, and they’re well worth the read.

1 Like