Capturing Form component attributes into a data structure

Hello!

I finally reached the point where I could use Data Binds, but still have questions after reading up a bit.

For a Form and each Component within it, how do each of their self.item[’ …’] relate to one another. Consider, for example, the tag attribute, which is available for a Form and virtually all Components within it …

If, say, for MyForm as well as for a Card component, a Drop-Down component, and a Nav-Link component within said MyForm, I Data-Bind their tag to self.item[‘foo’] – that is, to the identical dict() key string for each – how are each distinguished from one another? Does each have it’s own self.item[’…’]?

Understanding this will help me understand the data-structure and scope of self.item[…] (or for each self.item[…] if there are multiple in my contrived example) – crucially, so that I don’t clobber data. :sob:

Thank you in advance! :blush:

1 Like

The power of databinding is especially evident with repeating panels.

The very basic idea is:

  • You assign a list of items to a repeating panel: self.rp.items = [...]
  • Anvil uses the template form associated to the repeating panel self.rp to add a form instance per each item of self.rp.items, and assign that item to that form instance’s item.
  • The form instance has its own self.item automatically updated with its own item, whatever that item is

Typically the original list contains dictionary or dictionary like objects, like datatable rows or like any class that you may define as described in the “What to do” paragraph here.

Anvil helps you by pre-populating the expression in the databinding input box with self.item[] and tries to understand what makes sense inside the square brackets. For example, if it knows the type of the item contained in the list assigned to the repeating panel, it will auto complete with whatever keys are available for that dictionary. For example, if the list contains rows of a specific table, the autocompletion will suggest the column names.

Anvil suggests you should enter self.item[something], but that’s a python expression and you can put whatever you like. The only constraint is you shouldn’t use globals from other modules. Something like Globals.parameter_1 will not work. You will need to add self.parameter_1 = Globals.parameter_1 at the beginning of the the form.__init__, then use self.parameter_1 in the databinding.

I sometimes don’t even use self.item. For example, I am working now on a form that has def __init__(self, truck, **properties):, then I use self.truck.number, etc. in the databinding expressions, or self.truck in the databinding of the visible property of some components, so they are only visible if self.truck is defined.


I personally don’t like databinding because it’s just hidden python code, and I don’t like hidden python code. Creating a loop in the __init__ that takes care of assigning the values where they belong would do the same and would be more readable and searchable.

Said that, I use databinding all the times, because it’s so handy and easy to use.

I usd to never use databinding because errors in the code in the databinding expression used to be a nightmare to debug. They would just fail with very little feedback about the reason.

Then the errors started to show up as clickable links on the console (see here), so I started to use databinding.

Then that link stopped working (see here), but the information about the error is still there and it’s very helpful, so I still use databinding. A lot!

2 Likes

Hi @stefano.menci Thank you for the comprehensive answer and examples. I’ll re-read it once I get the hang of the basics. :slight_smile:

Let me isolate my question with something concrete. Let’ say we have this From design:

  • MyForm1 (contains):
    • card_component_enclosing_checkBoxes (contains):
      • chkBox_1DataBind :: checked → self.item[‘is_checked’]
      • chkBox_2DataBind :: checked → self.item[‘is_checked’]
      • chkBox_3DataBind :: checked → self.item[‘is_checked’]
      • chkBox_4DataBind :: checked → self.item[‘is_checked’]
      • chkBox_5DataBind :: checked → self.item[‘is_checked’]

I know there are various options, design-patterns, best-practices to inspect which chkBoxes are and aren’t checked. For instance, (forgetting about DataBinds for a moment – ignore them), this brute-force method can be done:

for component in self.card_component_enclosing_checkBoxes.get_components()
   if component.checked:
     [... execute code for a checked chkBox ...]
   else
     [... execute code for a not-checked chkBox ...]

I understand the above approach well.

So let’s go onto a second approach: DataBinds:

Using my design above, what is the correct expression to get to the self.item of each chkBox, so that I can inspect their respective dict(). Why? I keep getting blank dict() (i.e. { } ) back in my print() debug outputs when I execute print(someExpression); even though I was sure to tick chkBoxes.

What should someExpression be for each chkBox, based on the above design, so I don’t keep getting back: { }?

The fundamentals are what I’m after here because, apparently, I’m incorrectly indexing somewhere in my someExpression. :slight_smile:

Thank you!

There is no respective dict for each checkbox.

The form represents a document (or object or part or whatever).

self.item in the form is a dictionary describing that document.

Each component represents one (or more) property of the document.

For example, if self.item represents a truck:

# example of self.items
{
    'truck_number': '123',
    'is_closed': True,
    'has_shipped': False,
}

# examples of databindings
# databinding for the textbox self.truck_number
text    -> self.item['truck_number']

# databinding for the checkbox self.truck_is_closed
checked -> self.item['is_closed']

# databinding for the checkbox self.truck_has_shipped
visible -> self.item['is_closed']
checked -> self.item['has_shipped']

Or, if you use the object self.truck instead of the dictionary self.item:

# example of object self.truck
self.truck.number = '123'
self.truck.is_closed = True
self.truck.has_shipped = False

# examples of databindings
# databinding for the textbox self.truck_number
text    -> self.truck.truck_number

# databinding for the checkbox self.truck_is_closed
checked -> self.truck.is_closed

# databinding for the checkbox self.truck_has_shipped
visible -> self.truck.is_closed
checked -> self.truck.has_shipped

Then in your code you will have:

if self.truck_is_closed.checked:
    # manage closed truck
else:
    # manage open truck

But, since databinding can be bi-directional, you don’t even need to deal with the components. You can simply do:

if self.item['is_closed']:
    # manage closed truck
else:
    # manage open truck

# or:
if self.truck.is_closed:
    # manage closed truck
else:
    # manage open truck

I usually work with my own classes, not with dictionaries, so all I need to do is:

self.truck.update()

and, since the databinding is bi-directional and the properties have been updated, then the object attributes have been updated, so its update() method can do its job relying on fresh data.

This makes it easy to let the form do the form and my class manage the logic. The form defines the databinding between the input components and the attributes of my object, then calls one method to tell the object to process its new status, and the object does its job. This makes it very easy to test the business logic independently from the UI.

1 Like

This is a great example. I need to wrap my head around and keep re-reading it before moving on with my code.

Before moving on, should the following be: truck.is?

Okay, let me see if I captured what you conceptually explained. Are these correct as I echo them back? …

  1. There’s nothing exclusive about a Form’s self.item[''] dict() and the Data Bindings strategy. It’s simply a convenience provided for you by Anvil out-of-the-box (and in the Designer UI) for that purpose; and it being a dict() instance, was a reasonable choice because you can nest arbitrarily complex data-structures within it). Correct?

  2. If you do use self.item[''] (in lieu of -or- in addition to your own class instances), then you (a) mentally ascribe some Business Object meaning to self.item[''] (like a Truck, IoT device, or Student), and then (b) define appropriate dict() keys for it (i.e., define Business Object attributes), and finally (c) collect their respective values by binding said keys to specific component attributes scattered across your Form. Correct?

  3. In fact, self.item[''] alone may not be enough because you sometimes need to collect values for various Business Object instances. So, you instantiate your own custom Business Objects (as you illustrated), and bind them similarly. And the binding process here is, essentially, no different than for self.item['']. (except see below) Correct?

Are those three understandings Correct? Hopefully, yes.

Follow-up: For Item-3 above – let’s say it’s an IoT class – because the Designer UI has no component for it, the Data Bindings step must be done programmatically, Correct?

Thank you!

UPDATE: self.item appears to start life off as an identifier pointing to to a dict(), but (I think) can be replaced by any other data structure. Sorry @stefano.menci I know you’re typing a reply. Just had to insert that. :slight_smile:

No.
self.truck_is_closed is a checkbox. I Create a checkbox, I name it truck_is_closed and see if it’s checked with self.truck_is_closed.checked.

Some like to call checkboxes something like self.checkbox_truck_is_closed or self.ckb_truck_is_closed. I don’t like it. I like to keep the variable names as close to the natural language as possible. Makes the code much easier to read. Well… I understand that in this case it was confusing for you, but I feel like abandoning all those useless cluttering prefixes makes the code much more readable (try to google “Hungarian notation python” and you will find more frowners than fans).

Later I do mention self.truck, which is the instance of the unmentioned Truck class. In that case self.truck.is_closed makes sense, because we are looking at the is_closed property of the truck object.


  1. Correct.
    I suppose the Anvil team had in mind dictionary objects because they can represent a set of properties in general and because the simplest app can use databinding to read and store information in the database, via dict-like datatable row objects.

  2. Correct.
    One thing worth mentioning is that there doesn’t need to be a one-to-one relationship between components and dictionary elements. That’s why in my previous examples I used the is_closed attribute twice, once bound to the checked property of the self.truck_is_closed checkbox and once bound to the visible property of the self.truck_has_shipped checkbox.

  3. Correct.
    And that’s why I never use row objects for databinding the way it’s illustrated in most tutorials. Because the datatable have one column for each piece of information that needs to be stored, but I often use some logic to add other pieces of information. For example I sometime add attributes like background color or other calculated values. Yes, you can easily put the price in one column, the tax in the next and calculate the price+tax in the 3rd column on the form, but I don’t do that. Calculations belong in the business logic, not in the form. I would have 2 values stored in the datatable and 3 attributes in the object (whether it’s a dictionary or its own class), price, tax and price_plus_tax.


I don’t understand this question.

If you are talking about custom components, then it is possible to create read-only or read-and-write properties for the custom component and use them with databinding.

In general, if I don’t do databinding at design time, I write/read values to/from the components from code, I don’t setup databinding from code (I have never done it, I don’t know if it is possible).

1 Like

In general, if I don’t do databinding at design time, I write/read values to/from the components from code, I don’t setup databinding from code (I have never done it, I don’t know if it is possible).

I guess @anvil might chime in (whether or not possible)

I asked because I thought your earlier example implied setting up data-bindings against arbitrary objects (i.e., you mentioned – object "self.truck" instead of the dictionary "self.item"). But from the follow-up reply (immediately above), it looks like you’re performing manual copies (sans Anvil’s data-binding facility).

To be honest, Data Bindings isn’t documented well (though 99% of the docs are exemplary and beyond compare - I’ve mentioned that). I guess one can blame the newbie (me, and I’m fine with that); or, consider that if a newbie doesn’t fully understand data bindings, what can be improved and fleshed-out in the Data Bindings docs.

Anyway, I’ll continue my observational trials because that’s the way I’ll figure this all out.

Thank you for the inputs! :slight_smile:

Anvil has the convention of calling item any generic item.

When an app manages trucks or crates I pass Truck or Crate objects to the form.

Sometimes the form’s item is the truck, sometimes the truck is inside the item dictionary because the form needs other info not related to the truck.

Sometimes I’m lazy and use self.item['truck_number'], sometimes (always) I do either self.truck = self.item or self.truck = self.item['truck'], depending on how I pass the info to the form, then I work with self.truck. More readable than self.item.

The above applies to forms used as repeating panel templates, where Anvil automatically populate self.item. In top level forms I don’t even use item. I pass the truck object as an argument of the __init__ at form creation time.

1 Like

I’ll often use self.item in forms whose purpose is to be instantiated as the content of an alert. That way I can use content = ContentForm(item=whatever) and then Anvil takes care of putting whatever in as ContentForm’s self.item.

Lazy of me, but it saves me a line of code in ContentForm’s __init__, and then I don’t have to remember what to use in data bindings.

2 Likes

Hi and thank you.

After watching this tutorial just now, I may have unearthed (partly) the problem.

By example:

  • A user completes a survey Form containing various input component kinds. To mildly complicate things (but not by much), there are five (qty. 5) varieties of survey Forms. But whichever the variety, once the SUBMIT button is pressed, I need to capture component attribute content (i.e. what the survey taker inputted). Each variety will, of course, require different collection-code because their components differ. (Note: I’m not concerned that the code will differ. I don’t care).

I never need to write / update component attribute properties (ever). I only need to read them (i.e. what the user inputted). I thought Data Bindings would be helpful here, but I’m not so sure.

I’ll just programmatically determine the form kind and scrape component attribute content in code. It goes back to the title of this post: Capturing Form component attributes into a data structure.