Routing
The routing module allows hash-based navigation in an Anvil app.
Live Example: |
|
Example Clone Link: |
Introduction
An Anvil app is a single-page app. When the user navigates through the app’s pages the URL does not change. The part of the URL before the # is used by the server to identify the app. The part following the #, is never sent to the server and used only by the browser.
The routing module takes advantage of the URL hash and allows unique URLs to be defined for forms within an app. Here are a few examples of URL hashes within an app and associated terminology.
URL |
|
|
|
|
|
|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Template Forms
These are top-level forms.
A TemplateForm is not the HomeForm. A TemplateForm has no content.
It only has a navigation bar, header, optional sidebar and a content_panel
(This is based on the Material Design standard-page.html).
import the routing module
import all the forms that may be added to the
content_paneladd the decorator:
@routing.template(path, priority, condition)
from anvil_extras import routing
from .Form1 import Form1
from .Form2 import Form2
from .Form3 import Form3
from .ErrorForm import ErrorForm
@routing.template(path="", priority=0, condition=None)
class MainRouter(MainRouterTemplate):
An Anvil app can have multiple template forms.
When the url_hash changes the routing module will
check each registered template form in order of priority (highest values first).
A template form will be loaded as the open_form only if,
the current url_hash starts with the template’s path argument and either the condition is None
or the condition is a callable that returns True.
The path argument can be a string or an iterable of strings.
The above example would be the fallback template form. This is equivalent to:
@routing.default_template
class MainRouter(MainRouterTemplate):
If you have a different top-level template for the admin section of your app you might want a second template.
from .. import Globals
@routing.template(path="admin", priority=1, condition=lambda: Globals.admin is not None)
class AdminRouterForm(AdminRouterTemplate):
The above code takes advantage of an implied Globals module that has an admin attribute.
If the url_hash starts with admin and the Globals.admin is not None then this template
will become the open_form.
Another example might be a login template
from .. import Globals
@routing.template(path="", priority=2, condition=lambda: Globals.user is None)
class LoginRouterForm(LoginRouterTemplate):
Note that TemplateForms are never cached (unlike RouteForms).
Route Forms
A route form is any form that will be loaded inside a TemplateForm’s
content_panel.
Import the routing module
add the
@routing.routedecorator above the class definitionThe first argument to the decorator is the
url_pattern(think of it as the page name).The second argument is optional and is any
url_keys(a list of strings that make up a query strings in theurl_hash) (userouting.ANYto signify optionalyurl_keys)
from anvil_extras import routing
@routing.route('article', url_keys=['id'])
class ArticleForm(ArticleFormTemplate):
...
Or without any url_keys
from anvil_extras import routing
@routing.route('article')
class ArticleForm(ArticleFormTemplate):
...
Or with url_keys where there may be other optional keys
from anvil_extras import routing
@routing.route('article', url_keys=["id", routing.ANY])
class ArticleForm(ArticleFormTemplate):
...
Home form
The HomeForm is also a Route Form that appears in the content_panel of the loaded TemplateForm.
Import the routing module
add the
@routing.routedecoratorset the
url_pattern(page name) to an empty string
from anvil_extras import routing
@routing.route('')
class Home(HomeTemplate):
...
Error form (Optional)
This is the form that is shown when the url_hash refers to a page
that does not exist, or the query string does not match the url_keys
listed in the decorator. Follow these steps to create an error form that
shows an error message:
Create a form with the label
Sorry, this page does not existImport the routing module
add the decorator
@routing.error_form
from anvil_extras import routing
@routing.error_form
class ErrorForm(ErrorFormTemplate):
...
Startup Forms and Startup Modules
If you are using a Startup Module or a Startup Form all the TemplateForms and RouteForms must
be imported otherwise they will not be registered by the routing module.
If using a Startup module, it is recommended call routing.launch() after any initial app logic
from anvil_extras import routing
from .. import Global
# Setup some global data
Global.user = anvil.server.call("get_user")
if Global.user is None:
routing.set_url_hash("login", replace_current_url=True)
routing.launch() # I will load the correct template form
It is also ok to use anvil.open_form("LoginForm"), or to use a TemplateForm as the Startup Form.
In either case, the routing module will validate the template form is correct based on the registered templates for the app.
Dynamic Vars
An alternative to a query string is to include a dynamic URL hash.
The dynamic variables inside the URL pattern will be included in the dynamic_vars attribute.
from anvil_extras import routing
@routing.route("article/{id}")
class ArticleForm(ArticleFormTemplate):
...
You can then check the id using:
print(self.dynamic_vars) # {'id': 3}
print(self.dynamic_vars['id']) # 3
Multiple dynanamic variables are supported e.g. foo/{var_name_1}/{var_name_2}.
A dynamic varaible must be entirely contained within a / portion of the url_pattern,
e.g. foo/article-{id} is not valid.
Redirects
A redirect is similar to a template in that the arguments are the same.
@routing.redirect(path="admin", priority=20, condition: Globals.user is None or not Globals.user["admin"])
def redirect_no_admin():
# not an admin or not logged in
return "login"
# can also use routing.set_url_hash() to redirect
@routing.redirect(path="admin", priority=20, condition=lambda: Globals.user is None or not Globals.user["admin"])
def redirect_no_admin():
routing.set_url_hash("login", replace_current_url=True, set_in_history=False, redirect=True)
When used as a decorator, the redirect function will be called if:
the current
url_hashstarts with the redirectpath, andthe condition returns
Trueor the condition isNone
The redirect function can return a url_hash, which will then trigger a redirect.
Alternatively, a redirect can use routing.set_url_hash() to redirect.
Redirects are checked at the same time as templates, in this way a redirect can intercept the current navigation before any templates are loaded.
API
Decorators
- routing.template(path='', priority=0, condition=None, redirect=None)
Apply this decorator above the top-level Form -
TemplateForm.pathshould be a string or iterable of strings.priorityshould be an integer.conditioncan beNone, or a function that returnsTrueorFalse
The
TemplateFormmust have acontent_panel. It is often could to refer toTemplateForm``s with the suffix ``Routere.g.MainRouter,AdminRotuer. There are two callbacks available to aTemplateForm.- on_navigation(self, url_hash, url_patter, url_dict, unload_form)
The
on_navigationmethod, when added to yourTemplateForm, will be called whenever theurl_hashis changed. It’s a good place to adjust the look of yourTemplateFormif theurl_hashchanges. e.g. the selected link in the sidebar. Theunload_formis possibleNoneif this is the first load of the app.
- on_form_load(self, **nav_args)
- on_form_load(self, url_hash, url_patter, url_dict, form)
The
on_form_loadis called after a form has been loaded into thecontent_panel. This is also a good time to adjust theTemplateForm.
- routing.default_template
equivalent to
routing.template(path='', priority=0, condition=None).
- routing.route(url_pattern, url_keys=[], title=None, full_width_row=False, template=None)
The
routing.routedecorator should be called with arguments that determine the shape of theurl_hash. Theurl_patterndetermines the string immediately after the#. Theurl_keysdetermine the required query string parameters in aurl_hash.The
template, when set, should be set to a string or list of strings that represent valid templates this route can be added to. If notemplateis set then this form can be added to any template.The routing module adds certain parameters to a
Route Formand supports abefore_unloadcallback.- url_hash
The current
url_hash. Theurl_hashincludes the query. See Introduction for examples.
- url_pattern
The
url_hashwithout the query string.
- url_dict
The query string is converted to a python dict.
- dynamic_vars
See Dynamic URLs.
- before_unload(self)
If the
before_unloadmethod is added it will be called whenever the form currently in thecontent_panelis about to be removed. If any truthy value is returned then unloading will be prevented. See Form Unloading.
- routing.lazy_route(url_pattern, url_keys=[], title=None, full_width_row=False, template=None)
from anvil_extras import routing @routing.lazy_route('article', url_keys=['id', routing.ANY], title="Article-{id} | RoutingExample") def article_route(): from ..ArticleForm import ArticleForm return ArticleForm
This decorator allows you to lazily load Forms. When using
@routing.routeall Forms that are routes must be imported before the app starts. This is fine for most small applications, but as your application grows you may find that executing all the code for all the Forms is slow. Thelazy_routedecorator should decorate a function that imports the Form and returns it.
- routing.redirect(path, priority=0, condition=None)
The redirect decorator can decorate a function that will intercept the current navigtation, depending on its
path,priorityandconditionarguments.pathcan be a string or iterable of strings.priorityshould be an integer - the higher the value the higher the priority.conditonshould beNoneor a callable that returns aTrueorFalse.
A redirect function can return a
url_hash- which will trigger a redirect, or it can callrouting.set_url_hash().
- routing.error_form
The
routing.error_formdecorator is optional and can be added above a form that will be displayed if theurl_hashdoes not refer to any knownRoute Form.
Exception
Usually called inside the
on_navigationcallback. Prevents the current navigation from attempting to change thecontent_panel. Useful for login forms.
List of Methods
- routing.launch()
This can be called inside a Startup Module. It will ensure that the correct Template is loaded based on the current
url_hashand template conditions. Callingopen_form()on aTemplateFormwill implicitly callrouting.launch(). Untilrouting.launch()is called anvil components will not be loaded when theurl_hashis changed. This allows you to set theurl_hashin startup logic before any navigation is attempted. Similarly when aTemplateFormis loaded any routing is delayed until after theTemplateFormhas been initialized.
- routing.set_url_hash(url_hash)
- routing.set_url_hash(url_hash, **properties)
- routing.set_url_hash(url_pattern=None, url_dict=None, **properties)
- routing.set_url_hash(url_hash, *, replace_current_url=False, set_in_history=True, redirect=True, load_from_cache=True, **properties)
Sets the
url_hashand begins navigation to load a form. Any properties provided will be passed to the form’s properties. You can also pass the url_pattern and url_dict separately and let the routing module convert this to a valid url_hash. This is particularly useful when you have strings that need encoding as part of the query string.The additional keywords in the call signature will adjust the routing behaviour.
If
replace_current_urlis set toTrue. Then the navigation will happen “in place” rather than as a new history item.If
set_in_historyis set toFalsethe URL will not be added to the browser’s history stack.If
redirectis set toFalsethen you do not want to navigate away from the current form.if
load_from_cacheis set toFalsethen the new URL will not load from cache.Note that any additional properties will only be passed to a form if it is the first time the form has loaded and/or it is not loaded from cache.
- routing.alert(content, *args, **kws)
Use in place of
anvil.alert. If you useanvil.alertthen alerts will not close when the user navigates. This is probably not what you want. When usingrouting.alertany alert that isdismissiblewill close when the user navigates. Any non-dismissible alert will block the navigation.You may want to do
import anvil; anvil.alert = routing.alertas the first line in a startup module to overrideanvil.alertacross your app.
- routing.get_url_components(url_hash=None)
Returns a 3 tuple of the
url_hash,url_patternandurl_dict. If theurl_hashis None it will return the components based on the currenturl_hashof the page.
- routing.get_url_hash(url_hash=None)
Returns the
url_hash- this differs slightly from the Anvil implementation. It does not convert a query string to a dictionary automatically.
- routing.get_url_pattern(url_hash=None)
Returns the part of the
url_hashwithout the query string.
- routing.get_url_dict(url_hash=None)
Returns a dictionary based on the query string of the
url_hash.
- routing.load_error_form()
Loads the error form at the current
url_hash.
- routing.remove_from_cache(url_hash)
Removes a
url_hashfrom theroutingmodule’s cache.
- routing.add_to_cache(url_hash, form)
Adds a form to the cache at a specific
url_hash. Whenever the user navigates to this URL the cached form will be used. (Caching generally happens without you thinking about it).
- routing.clear_cache()
Clears all forms and url_hash’s from the cache.
- routing.get_cache()
Returns the cache object from the
routingmodule. Adjusting the cache directly may have side effects and is not supported.
- routing.go(x=0)
Go forward/back x number of pages. Use negative values to go back.
- routing.go_back()
Go back one page.
- routing.reload_page(hard=False)
Reload the current route_form (if
hard = Truethe page will refresh)
- routing.on_session_expired(reload_hash=True, allow_cancel=True)
Override the default behaviour for a session expired. Anvil’s default behaviour will reload the app at the home form.
- routing.set_warning_before_app_unload(True)
Pop up the default browser dialogue when navigating away from the app.
- routing.logger
Logging information is provided when debugging. Logging is turned off by default.
To turn logging on do:
routing.logger.debug = True.
Notes and Examples
The following represents some notes and examples that might be helpful
Routing Debug Print Statements
To debug your routing behaviour use the routing logger. Routing logs are turned off by default.
To use the routing logger, in your Startup Module
from anvil_extras import routing
routing.logger.debug = True
Page Titles
You can set each Route Form to have a title parameter, which will
change the browser tab title
If you do not provide a title then the page title will be the default title provided by Anvil in your titles and logos
@routing.route('', title='Home | RoutingExample')
class Home(HomeTemplate):
...
@routing.route('article', url_keys=['id'], title="Article-{id} | RoutingExample")
class ArticleForm(ArticleFormTemplate):
...
@routing.route('article/{id}', title='Article | {id}')
class ArticleForm(ArticleFormTemplate):
...
Think f-strings without the f
Anything in curly braces should be an item from
url_keysor a dynamic variable in theurl_pattern.
You can also dynamically set the page title, for example, to values loaded from the database.
from anvil.js.window import document
@routing.route('article', url_keys=['id'])
class ArticleForm(ArticleFormTemplate):
def __init__(self, **properties):
self.item = anvil.server.call('get_article', article_id=self.url_dict['id'])
document.title = f"{self.item['title']} | RoutingExample'"
self.init_components(**properties)
Full-Width Rows
You can set a Route Form to load as a full_width_row by setting
the full_width_row parameter to True.
@routing.route('', title='Home', full_width_row=True)
class Home(HomeTemplate):
...
Multiple Route Decorators
It is possible to define optional parameters by adding multiple
decorators, e.g. one with and one without the key. Here is an example
that allows using the home page with the default empty string and
with one optional search parameter:
@routing.route('')
@routing.route('', url_keys=['search'])
class Form1(Form1Template):
def __init__(self, **properties):
self.init_components(**properties)
self.search_terms.text = self.url_dict.get('search', '')
Perhaps your form displays a different item depending on the
url_pattern/ url_hash:
@routing.route('articles')
@routing.route('blogposts')
class ListItems(ListItemsTemplate):
def __init__(self, **properties):
self.init_components(**properties)
self.item = anvil.server.call(f'get_{self.url_pattern}')
# self.url_pattern is provided by the routing module
Setting a Route’s Template
@routing.route('foo', template="MainRouter")
class Foo(FooTemplate):
def __init__(self, **properties):
...
Setting a template argument determines which templates a route form can be added to. If no template is set then this route can be added to any template.
A template argument should be the name of the template or a list of template names.
@routing.route('foo', template=["MainRouter", "AdminRouter"])
class Foo(FooTemplate):
def __init__(self, **properties):
...
If you have a route that can be used on multiple templates, consider using / notation.
@routing.template('admin', priority=2, condition=lambda Globals.is_admin)
class AdminRouter(AdminRouterTemplate):
...
@routing.route('/foo', template="AdminRouter")
class Foo(FooTemplate):
...
In the above example, since the route "/foo" does not start with admin,
"admin/foo" will be a valid url_pattern for this route
This allows you to write a route for different templates and only specify the suffix.
@routing.template('admin', priority=2, condition=lambda Globals.is_admin)
class AdminRouter(AdminRouterTemplate):
@routing.template('accounts')
class AccountRouter(AccountRouterTemplate):
@routing.route('/foo', template=["AdminRouter", "AccountRouter"])
class Foo(FooTemplate):
The Foo route will be added for the url_patterns "admin/foo" and "accounts/foo".
Note that the cached version of the Foo form will be added to either templates. If you don’t want to use a cached version for different templates, you should use multiple decorators
@routing.route('/foo', template="AdminRouter")
@routing.route('/foo', template="AccountRouter")
class Foo(FooTemplate):
Form Arguments
It’s usually better to avoid required named arguments for a Form. Something like this is not allowed:
@routing.route('form1', url_keys=['key1'])
class Form1(Form1Template):
def __init__(self, key1, **properties):
...
All the parameters listed in url_keys are required, and the rule is
enforced by the routing module. If the Route Form has required
url_keys then the routing module will provide a url_dict with
the parameters from the url_hash.
This is the correct way:
@routing.route('form1', url_keys=['key1'])
class Form1(Form1Template):
def __init__(self, **properties):
key1 = self.url_dict['key1']
#routing provides self.url_dict
If you need a catch all for arbirtrary url_keys use url_keys=[routing.ANY].
Or combine routing.ANY with required keys url_keys=["search", routing.ANY].
Template Form Callbacks
There are two callbacks available for a TemplateForm.
on_navigation: called whenever theurl_hashchangeson_form_load: called after a form is loaded into thecontent_panel
on_form_load example:
If you want to use animation when a form is loaded you might use the
on_form_load method.
def on_form_load(self, **nav_args):
# this method is called whenever the routing module has loaded a form into the content_panel
form = nav_args["form"]
animate(form, fade_in, duration=300)
Note if you wanted to use a fade-out you could also use the
on_navigation method.
def on_navigation(self, **nav_args):
# this method is called whenever the url_hash changes
form = nav_args["unload_form"]
if form is not None:
animate(form, fade_out, duration=300).wait()
# wait for animation before continuing
Security
Security issue: You log in, open a form with some data, go to the next form, log out, go back 3 steps and you see the cached stuff that was there when you were logged in.
Solution 1: When a form shows sensitive data it should always check
for user permission in the form_show event, which is triggered when
a cached form is shown.
Solution 2: Call routing.clear_cache() to remove the cache upon
logging out.
Passing properties to a form
You can pass properties to a form by adding them as keyword arguments to routing.set_url_hash
def article_link_click(self, **event_args):
routing.set_url_hash(f'article?id={self.item["id"]}', item=self.item)
I have a login form how do I work that?
As part of anvil_extras.routing
Login forms are the default form to load if no user is logged in.
You could create a login template.
We don’t want the user to navigate back/forward to other routes within our app once the user has logged out.
You can avoid this by raising a routing.NavigationExit() exception in the on_navigation() callback.
@routing.template("", priority=10, condition=lambda: Globals.user is None)
class LoginForm(LoginFormTemplate):
def on_navigation(self, **url_args):
raise routing.NavigationExit()
# prevent routing from changing the content panel based on the hash if the user tries to navigate back to a previous page
def login_button_click(self, **event_args):
user = anvil.users.login_with_form()
if user is not None:
Globals.user = user
routing.set_url_hash("")
You may choose to use redirect functions to intercept the navigation.
@routing.redirect("", priority=10, condition=lambda: Globals.user is None)
def redirect():
return "login"
@routing.redirect("login", priority=10, condition=lambda: Globals.user is not None)
def redirect():
# we're logged in - don't go to the login form
return ""
@routing.default_template
class DashboardRouter(DashboardRouterTemplate):
...
@routing.template("login", priority=1)
class LoginRouter(LoginRouterTemplate):
def on_navigation(self, url_hash, **url_args):
raise routing.NavigationExit
# prevent routing from changing the content panel
def login_button_click(self, **event_args):
Globals.user = anvil.users.login_with_form()
routing.set_url_hash("", replace_current_url=True)
# let routing decide which template
Advanced - redirect back to the url hash that was being accessed
@routing.redirect("", priority=10, condition=lambda: Globals.user is None)
def redirect():
current_hash = routing.get_url_hash()
routing.set_url_hash("login", current_hash=current_hash, replace_current_url=True, set_in_history=False)
# the extra property current_hash passed to the form as a keyword argument
@routing.redirect("login", priority=10, condition=lambda: Globals.user is not None)
def redirect():
# we're logged in - don't go to the login form
return ""
@routing.default_template
class DashboardRouter(DashboardRouterTemplate):
...
@routing.template("login", priority=1)
class LoginRouter(LoginRouterTemplate):
def __init__(self, current_hash="", **properties):
self.current = current_hash
def on_navigation(self, url_hash, **url_args):
self.current = url_hash
routing.set_url_hash("login", replace_current_url=True, set_in_history=False, redirect=False)
raise routing.NavigationExit
# prevent routing from changing the content panel
def login_button_click(self, **event_args):
Globals.user = anvil.users.login_with_form()
routing.set_url_hash(self.current, replace_current_url=True)
# let routing decide which template to load
More advanced - to access the current url_hash that is stored in the browser’s history you can use
window.history.state.get.url.
@routing.redirect("", priority=10, condition=lambda: Globals.user is None)
def redirect():
return "login"
@routing.redirect("login", priority=10, condition=lambda: Globals.user is not None)
def redirect():
return ""
@routing.default_template
class DashboardRouter(DashboardRouterTemplate):
...
@routing.template("login", priority=1)
class LoginRouter(LoginRouterTemplate):
def on_navigation(self, **url_args):
routing.set_url_hash("login", replace_current_url=True, set_in_history=False, redirect=False)
raise routing.NavigationExit
# prevent routing from changing the content panel
def login_button_click(self, **event_args):
Globals.user = anvil.users.login_with_form()
from anvil.js.window import history
routing.set_url_hash(history.state.url, replace_current_url=True)
Alternatively, you could load the login form as a route form rather than a template.
@routing.default_template
class MainRouter(MainRouterTemplate):
def __init__(self, **properties):
if Globals.users is None:
routing.set_url_hash("login") # this logic could also be in a Startup Module
def on_navigation(self, url_hash, **url_args):
if Globals.user is None and url_hash != "login":
raise routing.NavigationExit()
# prevent routing from changing the login route form inside the content panel
@routing.route('login')
class LoginForm(LoginFormTemplate):
def __init__(self, **properties):
self.init_components(**properties)
def form_show(self, **event_args):
"""This method is called when the column panel is shown on the screen"""
user = anvil.users.get_user()
while not user:
user = anvil.users.login_with_form()
routing.remove_from_cache(self.url_hash) # prevents the login form loading from cache in the future...
routing.set_url_hash('',
replace_current_url=True,
redirect=True
)
# '' replaces 'login' in the history stack and redirects to the HomeForm
Separate from anvil_extras.routing
Rather than have the LoginForm be part of the navigation, you could
create a startup module that will call open_form("LoginForm") if no user is logged in.
The LoginForm should not have any anvil_extras.routing decorators.
Then when the user has signed in you can call open_form('MainForm').
The routing module will return to changing templates and load routes when the url_hash changes.
When the user signs out you can call open_form('LoginForm').
routing will no longer take control of the navigation. There
will still be entries when the user hits back/forward navigation (i.e.
the url_hash will change but there will be no change in forms…)
:smile:
It is a good idea to call routing.clear_cache() when a user logs out.
I have a page that is deleted - how do I remove it from the cache?
def trash_link_click(self, **event_args):
"""called when trash_link is clicked removes the """
self.item.delete() # table row
routing.remove_from_cache(self.url_hash) # self.url_hash provided by the @routing.route class decorator
routing.set_url_hash('articles',
replace_current_url=True,
)
And in the __init__ method - you will want something like:
@routing.route('article', keys=['id'], title='Article-{id}')
class ArticleForm(ArticleFormTemplate):
def __init__(self, **properties):
try:
self.item = anvil.server.call('get_article_by_id', self.url_dict['id'])
except:
routing.set_url_hash('articles', replace_current_url=True)
raise Exception('This article does not exist or has been deleted')
Form Show is important
since the forms are loaded from cache you may want to use the
form_show events if there is a state change
Example 1
When that article was deleted in the above example we wouldn’t want the
deleted article to show up on the repeating_panel
so perhaps:
@routing.route('articles')
class ListArticlesForm(ListArticlesFormTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
self.repeating_panel.items = anvil.server.call('get_articles')
# Any code you write here will run when the form opens.
def form_show(self, **event_args):
"""This method is called when the column panel is shown on the screen"""
self.repeating_panel.items = anvil.server.call_s('get_articles')
# silent call to the server on form show
An alternative approach to the above scenario:
set load_from_cache=False
That way you wouldn’t need to utilise the show event of the
ListArticlesForm
@routing.route('article', keys=['id'], title='Article-{id}')
class ArticleForm(ArticleFormTemplate):
def __init__(self, **properties):
try:
self.item = anvil.server.call('get_article_by_id', self.url_dict['id'])
except:
routing.set_url_hash('articles', replace_current_url=True, load_from_cache=False)
def trash_link_click(self, **event_args):
"""called when trash_link is clicked removes the """
self.item.delete() # table row
routing.remove_from_cache(self.url_hash) # self.url_hash provided by the @routing.route class decorator
routing.set_url_hash('articles',
replace_current_url=True,
load_from_cache=False)
Example 2
In the search example above the same form represents multiple
url_hashs in the cache.
No problem.
Whenever navigation is triggered by clicking the back/forward buttons, the
self.url_hash, self.url_dict and self.url_pattern are
updated and the form_show event is triggered.
def form_show(self, **event_args):
search_text = self.url_dict.get('search','')
self.search_terms.text = search_text
self.search(search_text)
Leaving the app
Routing implements W3 Schools onbeforeunload method.
This warns the user before navigating away from the app using a default browser warning. (This may not work on ios)
By default, this setting is switched off. To switch it on do:
routing.set_warning_before_app_unload(True)
To implement this behaviour for all pages change the setting in your Startup Module.
To implement this behaviour only on specific Route Forms toggle the
setting like:
def form_show(self, **event_args):
routing.set_warning_before_app_unload(True)
def form_hide(self, **event_args):
routing.set_warning_before_app_unload(False)
Or based on a parameter (See the example app ArticleForm code for a
working example)
def edit_status_toggle(status):
routing.set_warning_before_app_unload(status)
NB: When used on a specific Route Form this should be used in
conjunction with the before_unload method (see above).
View Transitions
The routing 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 routing.use_transitions(False)