Using data from nested form

Hi I have been through a few tutorials and am trying my first app. I’ve been struggling with something that seems like you shjould be able tyo do but can’t find examples in docs or forums. I’m getting quite discouraged as I thought I had a basic grasp of this and only really slightly extending on the News aggregator example.

I am trying to create an “add staff form”. It will eventually have all the usual fields you would expect. I decided it might be clever (and I would learn a good pattern) if I made the address as a nested form so that I might re-use it to input addresses unrelated to staff in other parts of the app (I know, “don’t build it till you need it”, but I am just trying to learn concepts).

I created am AddressEdit form with 3 text fields

  • unit
  • street_num
  • street_name

when I tried data binding the fields e.g. self[‘unit’] I got and error about " Did you initialise all data binding sources before initialising this component?"

I created a “StaffEdit” form with 2 text fields and 1 field which is the AddressEdit form (I dragged the AddressEdit form onto the design page UI for my StaffEdit form.)

  • surname (data binding self[‘surname’] )
  • first_name (data binding self[‘first_name’] )
  • addressedit (form, tried data binding but don’t know what i’m doing)

I have gone round many circles using (or not) data bindings etc. and trying different scenarios to get this so I can’t say everything I have tried in detail or paste exact code. I am using the same pattern from the News Aggregator app with the alert popup and “if save_clicked…”

Code Sample:

def add_staff_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    new_staff = {}
    save_clicked = alert(
      content=StaffEdit(item=new_staff),
      title="Add Staff",
      large=True,
      buttons=[("Save", True), ("Cancel", False)]
    )
    # If the alert returned 'True', the save button was clicked.
    if save_clicked:
      print(new_staff)

When the print statement runs I just get the surname and first name back. I believe at one stage I had a version where it returned an addressedit object as new_staff['address'] but I don’t remember what state the code was in when it got that far and it’s no longer working.

Am I even approaching this the right way? Is this a stupid idea? Should you be able to access the nested forms fields by using self.item of the parent field?

I would like to use the data from the address form to add to an addresses data table and the other info to populate the staff data table (with has a table link to the address table).

Welcome to the forum.

Without a clone link I can’t tell for sure, but I’m guessing you need to replace self['unit'] with self.item['unit']

As Stefano says, data bindings are normally to elements of self.item. When you’re creating a form that you’re populating (as opposed to a data grid), you need to get the right info into self.item.

Since you dragged the address edit form onto your other form, you need to specifically set the item property of it at the appropriate point in the code. Without knowing more about how everything is hooked together, it’s hard to say when that is.

data = {'unit':'foo', 'street_num':'foo', 'street_name':'foo'}
self.address_edit.item = data

I’m showing data being a dictionary, but it could also be a data table row that contains the required fields.

Thanks for the replies, I’m sorry for the mistake in my post as the data bindings I mentioned are in the form self.item['unit'].

I must be missing some crucial piece of understanding as I am not sure where in the code I need to set the data for self.item on the child form as you describe jshaffstall.

Also I cant figure out what data binding to use for the child form on the parent form (or if I need to). I have included a clone link and would be grateful if someone can take a look. The login for testing is admin@mail.com, p/w: test

https://anvil.works/build#clone:SZGAJEPLNJ7E2M4T=Y6UOLMJ72GG5VNZZOT4MLBYF

Admittedly, I don’t know a whole lot about Data Bindings, but it seems like you are doing everything right – the AddressEdit component’s own item property is updated every time I expected it to.

What might be going on, is that since your AddressEdit component is a component within the StaffEdit, it’s not updating the addr_dict you set as AddressEdit’s item in the Staff Form. I don’t know if this is a bug per se or if the fact that it’s a component within a component means that the underlying pointers in memory aren’t the same for AddressEdit’s item property and the Staff Form’s addr_dict. Either way, I’ll let Anvil Staff or anyone more in the know Python and/or Anvil speak to that.

Nonetheless, I was able to get the new_staff dict to keep up with updates to each of the fields in AddressEdit by setting the lost_focus and pressed_enter events of each of them to the following function:

  def update_parent_data_bindings(self, **event_args):
    self.parent.item.update(self.item)
    print(self.parent.item)

This is at least a workaround if there’s a bug or the Python pointers are just out of joint and gets you the data you need. And the way I reset it by updating the parent’s item property in this function means that it will work with any other form you add AddressEdit to like you were hoping :slight_smile:

You’re pretty close. In the following, you do not need to create an instance of AddressEdit in code, since you dropped an instance onto your StaffEdit form. I’m also making the assumption that the new_staff dictionary will eventually contain the address information:

  def add_staff_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    new_staff = {}
    save_clicked = alert(
      content=StaffEdit(item=new_staff),
      title="Add Staff",
      large=True,
      buttons=[("Save", True), ("Cancel", False)]
    )
    # If the alert returned 'True', the save button was clicked.
    if save_clicked:
      print(new_staff)

Then in your StaffEdit code you have to wire up the AddressEdit component’s .item property:

class StaffEdit(StaffEditTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    
    self.addr_box.item = self.item
1 Like

Thanks @jshaffstall that fixed it. I have only just gotten round to testing this, but thanks also to @stefano.menci and @duncan_richards12 for replying so quickly to the thread and welcoming me to the forum.

2 Likes