Query

Query parameters are encoded in a URL following a ?, e.g. /dashboard?tab=sales&page=1. Query parameters may be referred to by different names, e.g. search, search params, query params, etc.

In this routing library, we will refer to query as a dictionary of query parameters. And a query string will be the URL-encoded version of the query.

The query is best used to encode the state of the page. For example, if you have a dashboard page with a tab component, you can use the query to encode the active tab.

Let's say you have a dashboard page with a tab component. The tab component has 2 tabs, income and expenses.

from ._anvil_designer import DashboardTemplate
from routing.router import navigate, RoutingContext

class Dashboard(DashboardTemplate):
    def __init__(self, routing_context: RoutingContext, **properties):
        self.init_components(**properties)
        self.routing_context = routing_context
        routing_context.add_event_handler("query_changed", self.on_query_change)
        routing_context.raise_init_events() # raises the query_changed event

    def on_query_change(self, **event_args):
        query = self.routing_context.query
        self.tab_1.value = query.get("tab", "income")

    def tab_changed(self, **event_args):
        tab_value = self.tab_1.value
        navigate(query={"tab": tab_value})

Note that in the tab_changed event handler, we are navigating to the same path, and so, we don't need to include the path in the navigate call. If we want to be explicit, we can use path="./" or path="/dashboard".

By default, if the query parameters change, a new instance of the form will be loaded (even if cache_form is set to True). See cache_deps below for more details. When the query parameters change, we can listen for the query_changed event and update our page state accordingly.

Parsing Query Parameters

Since query parameters are encoded in the URL, we may need to decode them. It's also generally a good idea to ignore unknown query parameters and provide sensible defaults if any are missing or incorrect. This provides a better user experience.

from routing.router import Route

class DashboardRoute(Route):
    path = "/dashboard"
    form = "Pages.Dashboard"

    def parse_query(self, query):
        valid_tabs = ["income", "expenses"]
        tab = query.get("tab", "income")
        if tab not in valid_tabs:
            tab = "income"

        return {"tab": tab}

Using a query validator

You can use a validator library. And if the validator has a parse method, it can be used as the parse_query attribute.

from anvil_extras import zod as z

class DashboardRoute(Route):
    path = "/dashboard"
    form = "Pages.Dashboard"

    parse_query = z.typed_dict({
        "tab": z.enum(["income", "expenses"]).catch("income")
    })

Query encoding

The routing library can encode any JSON-able object as a query parameter. Where a query parameter is a str, int, float, bool or None, this will be flat.

e.g. ?foo=bar&baz=1&eggs=true will be decoded as {"foo": "bar", "baz": 1, "eggs": True}.

Note

If you have numbers in your query parameters, but these should actually be strings, you can convert these to str in your parse_query method.

For nested, JSON-able objects, i.e. lists and dicts, the routing library will encode the object as a JSON string in the query string.

e.g. foo=%5B1%2C+%22a%22%2C+true%5D' will be decoded as {"foo": [1, "a", true]}.

Loading a new instance of a form

By default, the routing library will load a new instance of a form when the query parameters change.

If you do NOT wish to load a new instance of a form when certain query parameters change, you can override the cache_deps method.

This method should return a dict of dependencies, which determine when a form and its data should be loaded from cache. The return value from cache_deps should be JSON-able.

from routing.router import Route

class DashboardRoute(Route):
    path = "/dashboard"
    form = "DashboardForm"
    cache_form = True

    def cache_deps(self, **loader_args):
        # this form is cached uniquely by the `path` only - there are no `query` dependencies
        # i.e. if the `tab` changes, we keep the same instance of the form
        return None

For more details on cache_deps, see the data loading section.