Anvil Extras
  • Guides
    • Installation
    • Contributing
    • Components
    • Modules
      • Animation
      • Augmentation
      • Authorisation
      • Hashlib
      • Logging
      • Messaging
      • Navigation
        • Introduction
      • NonBlocking
      • Persistence
      • Popovers
      • Routing
      • Serialisation
      • Storage
      • Theme
      • Utils
      • Zod
Anvil Extras
  • Guides
  • Modules
  • Navigation
  • View page source

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 for full_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.

View Transitions

The navigation module will add a transition to page changes by default. The transition is a simple fade in fade out and uses the browser ViewTransition api. To remove this behaviour set navigation.use_transitions(False)

Previous Next

© Copyright 2021 The Anvil Extras project team members listed at https://github.com/anvilistas/anvil-extras/graphs/contributors.

Built with Sphinx using a theme provided by Read the Docs.