Migrating

From an app that navigates with anvil.open_form

Define your routes. If you are not using params in any routes, you should be able to replace all calls to anvil.open_form with router.open_form. To begin with, make sure to set cached_forms to False. As you decide certain routes should have params, you will need to replace router.open_form with router.navigate. The keyword arguments to open_form will be a dictionary you pass to the form_properties argument of navigate.

A common pitfall will be that a Form could previously rely on the item property always being passed to the form. However, this will not be the case if a user navigates directly to the form. In this case, the item property will be None, and you will have to fetch the item based on the routing_context.

From anvil_extras.routing (HashRouting)

Define your routes. Each route definition will be similar to the hash routing @route decorator.

By default, when a route subclasses from Route, the routing library will call anvil.open_form on the matching route's form. For hash routing apps, this is not what you want. Instead, you should subclass from TemplateWithContainerRoute and set the template attribute to the template form.

from routing.router import TemplateWithContainerRoute as BaseRoute

BaseRoute.template = "MainTemplate"

class IndexRoute(BaseRoute):
    path = "/"
    form = "Pages.Index"

If you have a single template in your hash routing app, then set BaseRoute.template = "MyTemplate".

If you have multiple templates, then you can set the template attribute on individual routes. See the section below that elaborates on how to work with multiple templates.

set_url_hash

Instead of calling hash routing's set_url_hash method, use the navigate function.

Or, if the set_url_hash call is inside a Link's click handler, replace the Link with a NavLink/Anchor. Set the path and query attribute appropriately and remove the click handler.

full_width_row

If the route decorator uses full_width_row, you should configure the Route.template_container_properties attribute.

from routing.router import TemplateWithContainerRoute as BaseRoute

BaseRoute.template = "MainTemplate"

class IndexRoute(BaseRoute):
    path = "/"
    form = "Pages.Index"
    template_container_properties = {"full_width_row": True}

If you are using full_width_row on all routes then you can set the full_width_row attribute on the Route class.

from routing.router import TemplateWithContainerRoute as BaseRoute

BaseRoute.template = "MainTemplate"
BaseRoute.template_container_properties = {"full_width_row": True}

Replace regular HTML links with NavLink components. Instead of using click handlers and manually managing navigation, configure the path directly on the component (either in code or in the designer):


from ._anvil_designer import MainTemplate
from routing import router


class Main(MainTemplate):
    def __init__(self, **properties):
        self.nav_home.path = '/home'
        self.nav_settings.path = '/settings'
        self.init_components(**properties)

NavLinks provide several advantages: - Automatic active state management - Support for ctrl+click to open in new tab - Native browser link preview behavior - No need for click handlers - Improved accessibility

on_navigation callback

In hash routing the on_navigation method is called on the Template form when the hash changes. This is often used to update the active nav link in the sidebar. If you are using Link components in your sidebar, we recommend replacing these with NavLink components (see previous section).

If you want to keep your existing on_navigation method, you can achieve this through the router's event system. The router will emit a navigation event when the url changes.


from ._anvil_designer import MainTemplate
from routing import router


class Main(MainTemplate):
    def __init__(self, **properties):
        self.links = {"/": self.home_nav, "/about": self.about_nav}
        self.init_components(**properties)

    def on_navigate(self, **event_args):
        context = router.get_routing_context()
        for path, link in self.links.items():
            if path == context.path:
                link.role = "selected"
            else:
                link.role = None

    def home_nav_click(self, **event_args):
        router.navigate("/")

    def about_nav_click(self, **event_args):
        router.navigate("/about")

    def form_show(self, **event_args):
        router.add_event_handler("navigate", self.on_navigate)
        self.on_navigate()

    def form_hide(self, **event_args):
        router.remove_event_handler("navigate", self.on_navigate)

If you have multiple Templates, we recommend subscribing to the navigation event in the form_show method of your template form, and unsubscribing in the form_hide method. If you only have a single template, you can subscribe to the navigation event in the __init__ method of your template form and there is no need to unsubscribe.

Using multiple templates, redirects, and other advanced usage

When migrating from anvil_extras, you might have different templates for different parts of your application. For example, you might have: - A main template with navigation for authenticated users - A minimal template for authentication pages - A specialized template for admin sections

You can configure this by setting different templates for different routes. You can also configure redirects and caching settings within each route class. Below is an example with two templates - one for authentication and one for the main app.


from routing.router import TemplateWithContainerRoute as BaseRoute
from routing.router import Redirect
from .Global import Global  # if you have a module for global (cached) variables


class EnsureUserMixin:
    def before_load(self, **loader_args):
        # Runs this method before loading the route form.
        if not Global.user:
            # Redirect if there is no logged in user.
            raise Redirect(path="/signin")


class SigninRoute(BaseRoute):
    template = 'Templates.Static'  # Template for auth screen
    path = '/signin'
    form = 'Pages.Signin'
    cache_form = True  # Cache settings for this route form.


class SignupRoute(BaseRoute):
    template = 'Templates.Static'  # Template for auth screen
    path = '/signup'
    form = 'Pages.Signup'
    cache_form = True


class HomeRoute(EnsureUserMixin, BaseRoute):
    # This route and ones below inherits from two classes,
    # which propagates the redirect from EnsureUserMixin
    template = 'Templates.Router' # Main template for app
    path = '/app/home'
    form = 'Pages.Home'
    cache_form = True


class SettingsRoute(EnsureUserMixin, BaseRoute):
    template = 'Templates.Router'
    path = '/app/settings'
    form = 'Pages.Settings'
    cache_form = True


class AdminRoute(EnsureUserMixin, BaseRoute):
    template = 'Templates.Router'
    path = '/app/admin'
    form = 'Pages.Admin'
    cache_form = True

Note that redirects are no longer defined in the startup form, but the routes module. In addition, caching is defined for each route, not as a parameter in the navigation function (anvil_extras set_url_hash). Any common attributes and methods can be set on base classes or mix-in classes.