Dashboard form with embedded pages

I have a very similar question to the following:

I have a dashboard layout with embedded “blank” form as navigation bar on the left and several “blank” forms that could go in the panel in the main page.

I’m not sure what in addition I need beyond the previous solution of:

def nav_link_click(self, **event_args):
    # This method is called when the link is clicked
    self.content_panel.clear()
    self.content_panel.add_component(MySubForm())

Such that this function is called when the correct button on navigation is clicked.

Currenlty my main page has code:

from embedcontact import *
class main (mainTemplate):
  def __init__(self):
    self.init_components()
  def contact_link(self, **event_args):
    self.content_panel.clear()
    self.content_panel.add_component(embedcontact())
    pass

and navigation page has code:

class navigation3 (navigation3Template):
  def __init__(self, **properties):
    self.init_components(**properties)

  def button_4_click (self, **event_args):
    main.contact_link()
    pass

Error when you run it and click the “contact” button is:
NameError: name ‘main’ is not defined
at navigation3, line 14

Many thanks for your help!

Hi.
ok, so your “Main” dashboard page needs a content_panel (let’s name it “cpMain”).
You also need a Module (not a server module) - let’s call it “GlobalModule”. In there declare a variable -

main_form=None

In your Main form, add

import GlobalModule

and also this line -

def __init__(self, **properties):
    # You must call self.init_components() before doing anything else in this function
    self.init_components(**properties)

    # Any code you write here will run when the form opens.
    GlobalModule.main_form=self # <---------Add this line 

Now you have a globally accessible reference to this specific form , and you can manipulate it and any controls inside it from any other form as required. For example, in your navigation form -

import GlobalModule
   ...
GlobalModule.main_form.cpMain.clear()
GlobalModule.main_form.cpMain.add_component(....etc)

Does that help?

EDIT -
What Ian suggested as well in this link -

was to define a function in the Global Module to hide the actual implementation of showing the form. Something like this (untested) -

def ShowCustomers():
  main_form.cpMain.clear()
  main_form.cpMain.add_component(<<formname>>)

So from within your Navigation form you could just call -

GlobalModule.ShowCustomers()

That’s the idea, anyway. There’s probably a better way to implement that.

Hey,

While David’s suggestion will work, I think it’s a bit complex for what you want to do.

If I understand correctly, you just need one container form, with a set of links on the sidebar. When one of those links is clicked, it opens a panel form in the center.

Here’s a working example of what I would suggest (click to open it and see the source code):

https://anvil.works/ide#clone:SQWOFM5HTCNJLZDE=QYXDLFWMVKV4UU5K46KXVGYV

Crucial points:

  • I haven’t made a panel for navigation - all the Link components are just on the main form. This makes it easier to get at the content_panel, because it’s on the same form as the links!
  • To make your code run when the link gets clicked, double-click the link in the page designer (or scroll down to the click event in the Properties dialog) - that will make sure that the method gets called when that link gets clicked. Just calling a function contact_link() won’t make it happen when the link gets clicked - you need to explicitly wire it up.

Check out the working example - it should clarify things!

Update: Here’s a video walkthrough of how I built that app:

Again, to view the source:

2 Likes

This is great and works! Thanks very much!!
Video really helpful thanks

Update:

The example above works when you trigger navigation from the top-level form (MainForm in the video example). But what happens if you want to trigger the navigation from one of the inner panels (Form1 and Form2 in our example)?

The answer is that you can use the get_open_form() function to get a reference to the current top-level form. You can then get at its methods and components. Eg:

new_page = Form2()

get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(new_page)

You can find this information in the reference documentation here.

5 Likes

Nice, but just for my own sanity’s sake can you confirm this is a new feature? Because if it’s not I’m going stark staring mad! :slight_smile:

Fun as it is to make you doubt your sanity: It’s new this week :wink:

2 Likes

2 posts were split to a new topic: Get_open_form() returns None

Hi @david.wylie and @meredydd

Thanks for these helpful tips. I have a question about what is most “Anvil-ic” (best practice) about displaying a new page in an app. So far I have used three methods for this:

  1. open_page('my_new_page')

  2. self.content_panel.clear()
    self.content_panel.add_component(my_new_page())

  3. get_open_form().content_panel.clear()
    get_open_form().content_panel.add_component(new_page)

I can see that method 3 works well if you are sitting on a component that lives inside a repeating panel for example (or some other nested situation), and you want to reach back and clear the parent. But why not use something like:

self.parent.parent.content_panel.clear() # with more or less parents...

(I must admit that I have not always gotten that approach to work; only sometimes)

Could you help me to understand when to use each of these and what is preferred?

Sincerely,
Al

In my opinion …

(1) is best when you need a completely new page loaded, as the old one gets removed. This is best if you are not using a dashboard style form. For example, my landing page (the default page when the application is run) makes some checks to see if I’m logged in. If I am, then it used open_form() to either take me to the main form or take me to a custom login form (where, once I’m logged in it uses open_form() to send me to the main form). From therein, however, the main form is a dashboard and I use (3).

(2) is used when you are updating your current form in some way from within itself. If you are using a dashboard style form then the navigation links in the side bar would use this technique to show the pages in the main content content_panel.

(3) is used when you want to replace the main content_panel (contained in the main dashboard form) from a content form and not the navigation links. This is because you are one or more child forms removed from it and this takes you right back.

The self.parent.parent… may in some circumstances have its uses, but I wouldn’t use it over the methods above if at all possible. Whilst not “wrong” it’s harder to understand 3 months later, and relative paths are often harder to get right when you’re not where you think you are (either due to error or “something changing”)

Does that help?

2 Likes

For extra style points, make a method on your top-level form that switches out its content, then call that method from your inner form. That way, your forms deep inside your app don’t need to know so much about the widgets on your top-level form - all they need to know is that they’re asking that top-level form to “open page XYZ”. Eg:

get_open_form().open_inbox()

Then, in future, if you change how the top level form is organised, or want to add more bells and whistles (changing the title, say, or setting the relevant navbar link’s role to 'selected'), you only need to change the code in one place!

6 Likes

(that’s actually how I do it; I blame England getting to the semis…)

1 Like

Extremely helpful. Thanks David and Meredydd.

Al