Navigation
A client module for that provides dynamic menu construction.
Introduction
This module builds a menu of link objects based on a simple dictionary definition.
Rather than manually adding links and their associated click event handlers, the module does that for you!
Mode
There are two modes for the navigation module: "classic"
and "hash"
.
If using "classic"
mode, when a link is clicked, a Form registered with the navigation module is added to the content_panel
element of the currently open form.
If using "hash"
mode, when a link is clicked, navigation is taken care of using the routing module.
from anvil_extras import navigation
navigation.set_mode("hash")
navigation.set_mode("classic")
"classic"
mode is the default mode if no mode is set.
Menu
In the Main form for your app, add a
ColumnPanel
to the menu on the left hand side and call it"menu_panel"
Add a menu definition dict to the code for your Main form and pass the panel and the dict to the menu builder. e.g.
from ._anvil_designer import MainTemplate
from anvil import *
from anvil_extras import navigation
from HashRouting import routing
menu = [
{"text": "Home", "target": "home"},
{"text": "About", "target": "about"},
]
class Main(MainTemplate):
def __init__(self, **properties):
navigation.build_menu(self.menu_panel, menu)
self.init_components(**properties)
The above code will add ‘Home’ and ‘About’ links to the menu, which will open registered forms with names "home"
and "about"
respectively.
If using "hash"
mode, then the links will set the url hash to "home"
and "about"
Registered Forms
In "classic"
mode, in order for a form to act as a target of a menu link, it has to be registered with a unique name using the @navigation.register()
decorator.
from ._anvil_designer import HomeTemplate
from anvil_extras import navigation
@navigation.register("home")
class Home(HomeTemplate):
def __init__(self, **properties):
self.init_components(**properties)
In "hash"
mode, forms will need to be registered with the routing module. See the docs for routing.
Menu definition
Each item in the dict needs the 'text'
and 'target'
keys as a minimum. It may also include 'title'
, 'full_width'
, 'visibility'
and 'condition'
keys:
‘title’ can be a string or None. Determines the page title.
‘full_width’ can be True or False to indicate whether the target form should be opened with ‘full_width_row’ or not. (Only valid with
"classic"
mode - see routing documentation forfull_width_row
if using"hash"
mode)‘visibility’ can be a dict mapping an anvil event to either True or False to indicate whether the link should be made visible when that event is raised.
‘condition’ can be a callable that returns a bool. Use in conjunction with
check_conditions()
(see below)All other keys in the menu dict are passed to the Link constructor.
For example, to add icons to each of the examples above, a ‘Contact’ item that uses hash routing and a ‘Settings’ item that should only be visible when advanced mode is enabled:
from ._anvil_designer import MainTemplate
from anvil import *
from anvil_extras import navigation
from HashRouting import routing
navigation.set_mode("hash")
menu = [
{"text": "Home", "target": "home", "icon": "fa:home", "title": "Home"},
{"text": "About", "target": "about", "icon": "fa:info", "title": "About"},
{"text": "Contact", "target": "contact", "icon": "fa:envelope", "title": "Contact"},
{
"text": "Settings",
"target": "settings",
"icon": "fa:gear",
"visibility": {
"x-advanced-mode-enabled": True,
"x-advanced-mode-disabled": False
},
"title": "Settings"
}
]
@routing.main_router
class Main(MainTemplate):
def __init__(self, **properties):
self.advanced_mode = False
navigation.build_menu(self.menu_panel, menu)
self.init_components(**properties)
def form_show(self, **event_args):
self.set_advanced_mode(False)
Startup
In order for the registration to occur, the form classes need to be loaded before the menu is constructed. This can be achieved by using a startup module and importing each of the forms in the code for that module.
e.g. Create a module called ‘startup’, set it as the startup module and import your Home form before opening the Main form:
from anvil import open_form
from .Main import Main
from . import Home
open_form(Main())
Page Titles
By default, the menu builder will also add a Label to the title slot of your Main form.
Titles will be set based on the menu definition passed to build_menu
.
If you want to disable this feature, set the with_title argument to False when you call build_menu in your Main form. e.g.
class Main(MainTemplate):
def __init__(self, **properties):
navigation.build_menu(self.menu_column_panel, menu, with_title=False)
self.init_components(**properties)
Navigate with Code
You can emulate clicking a menu link using the go_to
function, which takes a 'target'
key as its only parameter, e.g.
navigation.go_to("contact")
Conditional menu items
If you have conditions to determine whether a menu item should be shown you can use the condition key in menu definition
from functools import partial
def is_logged_in():
return anvil.users.get_user() is not None
def has_permission(permission):
user = anvil.users.get_user()
if user is None:
return False
if isinstance(permissions, str):
required_permissions = set([permissions])
else:
required_permissions = set(permissions)
user_permissions = set(permission["name"]
for role in user["roles"]
for permission in role["permissions"])
return required_permissions.issubset(user_permissions)
is_admin = partial("admin")
from anvil_extras import navigation
menu = [
{"text": "Home", "target": "home"},
{"text": "Dashboard", "target": "dashboard", "condition": is_logged_in},
{"text": "Admin", "target": "admin", "condition": is_admin}
]
class Main(MainTemplate):
def __init__(**properties):
...
navigation.build_menu(self.menu_panel, menu)
def login_button_clicked(self, **event_args):
user = anvil.users.login_with_form()
navigation.check_conditions()
def logout_button_clicked(self, **event_args):
anvil.users.logout()
navigation.check_conditions()
Note in the above example you might want to use a cached user since anvil.users.get_user()
will require a round trip to the server, i.e. one server call per condition.