Accessing Child Form Loaded in Parent Form

That’s an interesting thought…I’ll try it but I don’t see how that can be the issue. Basically, the structure is like this…

Home() form with “panel_main” column panel

add “PanelCaseDashboard()” form to “panel_main”

add/remove components on “panel_cd_center” which is a column panel inside the “PanelCaseDashboard()” form

I would agree, it is a shot in the dark.

Please forgive the messy code…

Code in question begins at line 57 here: https://anvil.works/new-build/apps/WWDGZEUN4MZF3ZT7/code/forms/Home.NavCases#code:60:50
https://anvil.works/build#clone:WWDGZEUN4MZF3ZT7=MX45WHLEKOJZKHDQ2NU6JM5Q

Maybe this should be done with a function from server code?

Maybe, but I don’t see how doing it on client or server side will render different results. If I get it working on client side, then server side is a breeze.

Something seems to happen when I do it server-side. I think that server side coding will enable us to update components. However, it gives me an error somewhere else in the code, line 27 and 24.

In my test, I first emptied the container (no data grid there, just the container. Then added this server function:

def update_panel_cd_center():
  Panels.PanelCaseDashboard().panel_cd_center.add_component(Panels.PanelAgenda)

then called it from line 57 where the other code was. The spinner started to move when I clicked the button, but I got this error code:

image

1 Like

Super interesting…now my wheels are spinning lol I’ll check out the server side in the morning and post back if I find a solution. Really appreciate all the brainstorming and help @larstuff

Well this is really too complex for me, but hope it helped some. Bye. I’ll look by tomorrow, see if there is progress. For whatever is left of it, good night!

BTW: Even if I edited out those lines of code with content update, the panel would not show up in the container.

1 Like

Sorry to say, no. That’s what I originally thought when I started working with Anvil, but it didn’t work.

To see why, think about what “server-side” and “client-side” actually means.

  • Client-side code runs in the end-user’s browser. This is where visual components exist. This is where they are created, manipulated, and updated.
  • Server-side code runs on Anvil’s servers, entirely separate computers that reside hundreds or thousands of kilometers from the end-user’s computer.

Two computers, far apart, connected only by wire (if that), and even then only indirectly, through many other hardware devices. These two computers don’t share memory. At all. So code running in the Server has no access whatsoever to Client-side components, because they reside only in the Client computer’s memory.

Any changes to Client-side components, therefore, have to be done by code running on the Client, i.e., on the end-user’s browser.

1 Like

@p.colbert any idea what I am doing wrong to target/change the contents of the “panel_cd_center” based on this little outline?

Home() form with “panel_main” column panel

add “PanelCaseDashboard()” form to “panel_main”

add/remove components on “panel_cd_center” which is a column panel inside the “PanelCaseDashboard()” form

I can no longer clone your most recent link above. However, my programmer’s eye is drawn to this code as an example of the kind of thing that may be going wrong:

What does the following part of the code actually do?

does it return an entirely new object of type PanelCaseDashboard, or does it return a reference to your existing dashboard object?

If it’s returning a new object, then you’re adding a component to that new object, and letting the new object be deleted at the end of the function.

Usually, if you’re adding to an already-created component, that component is a member of self. For example, one of my Forms contains a FlowPanel which I’ve named fp_scenario_ranking_method. In any of that Form’s member functions, it must be referred to as self.fp_scenario_ranking_method. If I wanted to add a new component to that FlowPanel, I’d call self.fp_scenario_ranking_method.add_component(c), where c is whatever code I need to create that new component.

It boils down to “what’s in a name”: what is Panels.PanelCaseDashboard? Based on how I’ve seen Anvil work to date, Panels is likely to be a Python package, which defines a Form class. Panels would name the class, not any particular instance of that class. Panels.PanelCaseDashboard would then name the class of a Form defined within that package. Again, it names not any particular instance, but the class, or “factory”, that makes instances. Append () to the name of any class, and you’re asking the “factory” to make a new instance. And unless you assign the result to some long-lived variable, that brand-new instance will be destroyed as soon as the enclosing function call (to update_panel_cd_center()) finishes running.

Since you haven’t shown the relevant code, and I can’t clone the app to see for myself, this is all guesswork on my part. So take it with a grain of salt.

2 Likes

Thank you, @p.colbert Here’s a new clone link: Anvil | Login

When I run

print(Panels.PanelCaseDashboard())

I am returning this: <PM.Panels.PanelCaseDashboard.PanelCaseDashboard object>

The print statement is at line 60, here: Anvil | Login

The IDE’s Search feature cannot find update_panel_cd_center anywhere in the clone.

image

Edit: However, I was able to confirm my speculations.

  • Panel is a package
  • PanelCaseDashboard is a class in that package
  • PanelCaseDashboard() will create an entirely new instance of that class.
  • panel_cd_center is a component inside that class.
    • From within any member function of PanelCaseDashboard, it would be referenced as self.panel_cd_center

But Search sees no such references.

Here’s an updated clone link: Anvil | Login

I added the function back in and a “long_lived_variable”

Thank you, @adam. That makes things a lot clearer. In your existing code:

  def link_case_dashboard_click(self, **event_args):
    self.reset_links()
    event_args['sender'].role = 'selected'
    get_open_form().panel_main.add_component(Panels.PanelCaseDashboard(), full_width_row=True)
    self.nav_case_dashboard.visible = True
    self.reset_links_cd()
    self.link_cd_events.role = 'selected'
    self.nav_reports_dashboard.visible = False

  def link_reports_click(self, **event_args):
    self.reset_links()
    event_args['sender'].role = 'selected'
    get_open_form().panel_main.add_component(Panels.PanelCaseReports())
    self.nav_reports_dashboard.visible = True
    self.reset_links_rd()
    self.link_rd_documents.role = 'selected'
    self.nav_case_dashboard.visible = False

  # test function for forum question
  def update_panel_cd_center(self):
    Panels.PanelCaseDashboard().panel_cd_center.add_component(Panels.PanelAgenda)

If I understand correctly, in the above, where you have the second occurrence of Panels.PanelCaseDashboard(), you’d really rather be referring to the result of the first occurrence above. Is that correct?

@p.colbert no! thank you for all the help!!!

Per your question, we have panel_main which is populated with Panels.PanelCaseDashboard. Within Panels.PanelCaseDashboard, we have panel_cd_center. When Panels.PanelCaseDashboard is open, I need simply clear/add components to panel_cd_center within Panels.PanelCaseDashboard. Not sure if that makes sense?

It looks as though you believe that Panels.PanelCaseDashboard is the name of a specific instance of your dashboard. It is not. It is a factory for dashboards. See Everything is an object.

Each time your code calls Panels.PanelCaseDashboard(), it creates and returns a new dashboard, independent from any previously-created object. Its type is named Panels.PanelCaseDashboard, but the instance is just a (complicated!) value. You can assign that value to a variable, or not, as needed.

The first invocation (reformatted for clarity)

get_open_form().panel_main.add_component(
  Panels.PanelCaseDashboard(),   # creates a new dashboard as 1st argument
  full_width_row=True
)

creates a dashboard and immediately adds it, anonymously, to the current Form’s panel_main member. Since this dashboard has no name, it is hard (but not impossible) to refer to it later.

In the second usage, update_panel_cd_center:

  def update_panel_cd_center(self):
    Panels.PanelCaseDashboard().panel_cd_center.add_component(Panels.PanelAgenda)

When called, update_panel_cd_center will create an entirely new dashboard, different from the previous dashboard. Unless something refers to it, this (also) unnamed dashboard will live until the function returns.

You can see for yourself that each result of calling Panels.PanelCaseDashboard() produces a different object. Just print each object’s id().

db = Panels.PanelCaseDashboard()
print(id(db))

Edit: In sum, the core of the confusion here lies in the distinction between a class and an instance of that class. In JavaScript, the distinction has not always been clear. In Python, they’ve always been two different things.

1 Like

@p.colbert this makes more sense as to the issue - I printed the id’s on the ‘link_case_dashboard_click’ and I see the numbers incrementing with each link click. Would you mind suggesting a cleaner way to accomplish this task?

Since you want both functions to refer to the same instance, let’s give it a (durable) name, at the point where it is created.

Old way:

    get_open_form().panel_main.add_component(
      Panels.PanelCaseDashboard(),   # creates a new dashboard as 1st argument
      full_width_row=True
    )

New way:

    # create new dashboard, and preserve a reference to it as a new member of self:
    self.panel_case_dashboard = Panels.PanelCaseDashboard()
    # make the new dashboard part of the currently-displayed form:
    get_open_form().panel_main.add_component(
      self.panel_case_dashboard, 
      full_width_row=True
    )

Old way:

  def update_panel_cd_center(self):
    Panels.PanelCaseDashboard().panel_cd_center.add_component(Panels.PanelAgenda)

New way:

  def update_panel_cd_center(self):
    new_agenda_panel = Panels.PanelAgenda()
    # add the panel to the correct part of the form:
    self.panel_case_dashboard.panel_cd_center.add_component(new_agenda_panel)

self.panel_case_dashboard works, in this case, because both update_panel_cd_center and link_case_dashboard_click refer to the same self object, that is, the same Form. If these functions belonged to two different objects, then there would be more “navigation” or “lookup” code, to find the right containing object.

If there’s any chance that update_panel_cd_center will be called before link_case_dashboard_click, then we have to be more careful. In that case, self.panel_case_dashboard won’t have been assigned yet. In that case, set self.panel_case_dashboard = None in __init__. This will at least ensure that the variable self.panel_case_dashboard reliably exists when update_panel_cd_center is called, even if it doesn’t always contain a useful value. Then we can amend our last function as follows:

  def update_panel_cd_center(self):
    if self.panel_case_dashboard:
      new_agenda_panel = Panels.PanelAgenda()
      # add the panel to the correct part of the form:
      self.panel_case_dashboard.panel_cd_center.add_component(new_agenda_panel)

Note: I didn’t bother with adding new_agenda_panel to self. (You could, if you need to refer to it later, from other functions.) As it stands above, variable new_agenda_panel will cease to exist when its enclosing function returns. However, self.panel_case_dashboard.panel_cd_center will still retain a reference to the object, in its internal list of components, so the panel itself will “stay alive”, as intended.

I hope this resolves the class/instance confusion. JavaScript code often doesn’t make much of a distinction, so it pops up here from time to time. Python is more like Java and C++ in making a hard distinction.

You may have similar situations in other parts of your code.

Edit: I edited the above code for illustration purposes only. Errors (if any) are entirely my fault, and are left as an exercise for the reader to resolve.

2 Likes

@p.colbert Can’t thank you enough, really appreciate the insight and assistance :pray: