Trouble with simple navigation from one Form to the next

Hello Friends:

I’m having trouble navigating from one Form to the next.

For conciseness, I elided much of the code for both Forms below.

The startup Form is called Main (inside package ./Forms), and it’s code is shown next. It’s purpose is to run login-logic, then quickly move on to the next Form, which is called LevelSelector (also inside package ./Forms).

from ._anvil_designer import MainTemplate
from anvil import *
import anvil.microsoft.auth
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
#
from ...util.helpers import login
from ..LevelSelector import LevelSelector

class Main(MainTemplate):
    def __init__(self, **properties):
        self.init_components(**properties)
        login.attempt_login() # Completes user login, or logs them out.
        open_form(LevelSelector())

Code for LevelSelector Form:

from ._anvil_designer import LevelSelectorTemplate
from anvil import *
import anvil.microsoft.auth
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

class LevelSelector(LevelSelectorTemplate):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.

    self.label_grade.bold = True
    self.label_grade.visible = True
    self.drop_down_grade.visible = True
    print('Got Here!')

While the LevelSelector Form contains numerous components (more than is shown in the elided code above), it never renders.

When I run the above, the Output debug panel indeed prints ‘Got Here!’, however the LevelSelector Form never actually renders. That is, the Main Form still remains rendered.

With apologies for the basic question (I’m still getting familiar).

Any ideas or suggestions?

Thank you in advance.

Oh, I think I see the issue (maybe). I looks like init_components is missing.

EDIT: No that wasn’t it, thought it was missing (due to a cut/paste error).

This has been covered elsewhere in the Forum, but think about when Main.__init__ gets called. It’s called in order to construct your main form. That is, your main form can’t even be displayed until this function ends; and, once it does, then Anvil will display the Main form.

For your purposes, it looks like you might want a startup module instead of a startup form.

The other usual alternative is to open your instance of LevelSelector later, when the Main form is first displayed. That is, in response to the Main form’s show event.

Whichever you choose, Anvil’s docs have all the details you need. Highly recommended.

2 Likes

Thank you for the reply. Indeed, I’ve been reading them (including Form navigation).
@p.colbert The sequence you described does make sense (about being unable to render the Form until function return). I’ll try your advice and try a startup module.

EDIT: Actually, handling the Main Forms show event is probably more in line with what I need. Both suggestions were very helpful, though.

Thank you.

@p.colbert Yep, the latter worked well. Thank you very much.

Hi, it’s me again, that guy that answers Y when you ask X.

Since you are exploring the form navigation, I strongly suggest you to look into Anvil Extra’s routing module.

I use it in all my apps, couldn’t live without it.

For example, when a form has a search input box, the text box change event, instead of doing its job and updating the interface, reroutes to the same page with the new query parameters. Here is the line that does it on one of my apps, on a form with 3 input elements plus the page to go to later:

# definition of the form
@routing.route(
    'releases', 
    title='Releases - Crating',
    url_keys=['cmd', 'search', 'status', 'facility'])
class Releases(ReleasesTemplate):
    [...]

# inside search_click, called by the search button "click" and
# the 3 text boxes "change" events
    routing.set_url_hash(url_pattern='releases', url_dict={
        'search': self.search_text.text,
        'status': self.status_filter.text,
        'facility': self.facility_filter.text,
        'nextpage': self.next_page,
    })

Then the form_show event takes care of updating (actually building) the interface.

It may look like you are doing a round trip every time you change an input element, but in reality this is still a single page app, and you are only changing the part of the url after the #, so it doesn’t trigger a round trip. It just updates the url on the browser (so the user can copy/paste it) and re-renders the form. When the forms are not too complex this is the way to go. When the forms are complex and I want to update only part of the form, then things get a little more complex.

3 Likes

Hi @stefano.menci Always nice when you drop in. :relaxed:

Thank you for that example. I was unaware of the Anvil Extras toolbox, including its Routing contribution. I’ve been reading a ton, but it’s all a firehose right now (between the Anvil documentation – which is extremely well-written by the way, and Forum contributions).

I just read up on the Routing documentation you pointed me to. Indeed my App is probably still too simple to leverage it:

  1. A Landing Form
  2. Login functionality (using 3rd-Party providers).
  3. And 4-Forms, each of which need to share Three labels; Three Drop-downs, and the Three populated items for them. Like this, but flowing from Form-1 to the next, to the next:


All inside one “Flow Panel”.

So if Form-1 is used to select the drop-down items, then as I navigate to subsequent Forms (Form-2, Form-3, Form-4), I need to bring along those same Labels, Drop-downs and Selected items before the Python identifier for Form-1 goes away. Basically, navigation across Forms that share those components and property values.

That’s what I’m researching now. :blush:

What value should the parent member of a Label have, if it is shared by 4 forms?

I suppose this is one way:

In Form1:

class Form1(Form1Template):
  def __init__(self, **properties):
      self.init_components(**properties)
      # [ ... snip ... ]

   # Select drop-down items in this form.

  def some_event_handler(self, **event_args):
      open_form(Form2(source_form_props=self))

And in Form2, Form3, and Form4:

class Form2(Form2Template):   # --OR--
class Form3(Form3Template):   # --OR--
class Form4(Form4Template):   # --OR--
  def __init__(self, **properties):
      self.init_components(**properties)
      d = properties['source_form_props'] # A dict() of Form1 properties.
      
      # Now, subscript 'd' (ie, d['aKey']) to populate
      # various form-component properties in these "like-forms".

So, I created a Form with the components seen in the image above (along with additional common components); selected use as component for that Form; and finally, added it to Form1, Form2, Form3, and Form4. In this way, the component attribute and property names will be the same for all four Forms, and the dictionary d['aKey'] (k/v pairs) will work for all of them.

That’s the theory anyway. I’m trying it out now.

There are probably better ways, but I’m new to this framework.

This is exactly how custom components are supposed to be used. :smiley: So you’re absolutely on the right track.

Strictly speaking, the components themselves are not being shared. Each Form that uses them has its own copy. Every component has exactly one parent. But the data behind them, in Python variables, can be shared.

1 Like

@p.colbert Excellent. Thank you for the help, feedback, and clarification. :grinning:

Most of my one form apps use the routing module.

I decide whether to use it not based on app complexity, but on whether I need urls to point to a specific page, document, item, whatever.

For example, an app that sends an email saying “here is something for you” with a link containing something’s id, will use the routing module.


In Anvil there are two main ways to show the next form:

  • showing the next form, for example with open_form(Form2) that replaces the current form, that is the current form doesn’t exist anymore
  • keeping the main form where it is, including header, footer, navigation bar, etc., clearing the content of one of its containers and loading Form2 in that container

The routing module uses the second approach, and when I don’t use the routing module, I still use the same approach. I have one Standard Theme app with the styles and the look approved by the company, so every time I create a new app I clone it, delete its themes folder, copy the one from Standard Theme and push. At this point the new app looks like all the other apps, with the same navigation bar, header and footer. There is one container ready to be used, either directly or via the routing module to load all the forms.

get_open_form() always returns the main form.
get_open_form().content_panel.get_components()[0] always returns the loaded form.

The main form can also be used as a singleton, container for the globals (it’s better to use a module dedicated to this purpose, but in very simple apps that’s good enough).

1 Like

This was a great explanation and introduction to that strategy / pattern.

For my inaugural Anvil App (my very first one, a prototype last week), I implemented something similar in spirit with just one Form (and no more), and a much more junior technique: I created two UI variations of what I needed in two containers within the same single Form; then toggled visibility depending on what the business logic required. That’s all I could think of based on what I knew at the time. LoL :laughing: But it worked pretty well. That’s really a testament to the platform, which is quite intuitive.

Once I get a breather, I’ll sit with a couple of Forms and try what you described above to learn it.

I’m a long time backend data & analytics architect, so sadly these patterns aren’t as familiar to me, but it’s getting better. The People, Platform (Anvil Works) and Python have made it fun.

1 Like

Hello @stefano.menci

I knew I’d return to this helpful reply post.

Why? Because my App uses just one Material Form (the one that Anvil provides) with two stacked column-panels in it (for few drop-downs, labels, a button, and rich text box components). Let’s call this top-level Form Main.

Below that top area (with two column-panels) in Main is where I want to programmatically pull in one of three Sub-Forms, which I created from Blank Forms. Which one (of the three) I pull in depends on which drop-down item a user has selected in the Main Form.

That was all in preparation for the following (LoL) …

In the IDE, I’m in the Main Form, but when attempt the following (just to see the options):

  def __init__(self, **properties):
    self.init_components(**properties)

    form = get_open_form()
    form. # <-- No pop-up box after typing dot.

no pop-up selection box appears after typing dot.

The reason I ask is because I’m working towards the example snippet in this doc, which employs get_open_form(). This, I think, is how I pull Sub-Form1 into Main.

Thank you!

Move your code to a form_show event instead of __init__. In the __init__ of your main form, there is no open form yet. The form doesn’t actually get opened until after the init runs.

2 Likes

Ah, okay. Thank you. I wondered about that (ie, being too soon), but it didn’t click. Thank you replying. :slight_smile:

1 Like

Doing it in form_show(self, **event_args) (or anywhere else besides __init__() is certainly correct. Sadly, code-completion under the form_show(self, **event_args) event-handler isn’t popping-up either. I refreshed the IDE web-page just in case. (Above, I replied via my phone before trying it. That’ll show me. :slight_smile: ). Hmm. I’ll keep tinkering.

UPDATE: But I’m not getting a None exception when I move the code to form_show(self, **event_args) as suggested, which is good. Just no code-intelligence / pop-up dialogue. :man_shrugging:

get_open_form() returns the top level form, which is always derived from Form, but it is not Form.

In your case it is Main. You know it, but the auto complete doesn’t, that’s why it doesn’t work.

Unfortunately type hinting doesn’t help on the client (yet?).

1 Like

Thank you. :slight_smile:

Because hinting isn’t available, I’m in trial & observe mode until I discover the pattern. But consider this, which might help me get there faster:

  [ ... snip ... ]
    form = get_open_form()  # Get object to top-level Form (Main).
    form.cpanel_foo.clear() # Clear ColumnPanel within it named: cpanel_foo
    form.cpanel_foo.add_component(MySubForm()) # Instantiate MySubForm Form into ColumnPanel: 'cpanel_foo'
  [ ... snip ... ]

Give that, how do I get a handle to the instance of MySubForm inside (instantiated in) cpanel_foo so that I can set attributes / settings of components within that sub-form? For example, setting a YouTube URL for a video component (which has a name too, of course) inside that MySubForm instance.

In other words, what is the dot-notation and subscripting ([n]) pattern to access a nested hierarchy of Forms and components within those Forms?

As always, thank you very much.

Either use get_components():

form = get_open_form()
form.cpanel_foo.clear()
form.cpanel_foo.add_component(MySubForm())
sub_form = form.cpanel_foo.get_components()[0]

Or Instantiate the form first:

form = get_open_form()
form.cpanel_foo.clear()
sub_form = MySubForm()
form.cpanel_foo.add_component(sub_form)

The cool thing is that a form object can be added and removed and, as long as there is a variable referencing it, it will still exist. The routing module allows (optionally) you to cache the forms by keeping a dictionary with all the urls visited and showing the cached one instead of re-generating it.

1 Like