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_panel
add 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.route
decorator 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.ANY
to 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.route
decoratorset 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 exist
Import 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_hash
starts with the redirectpath
, andthe condition returns
True
or 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
.path
should be a string or iterable of strings.priority
should be an integer.condition
can beNone
, or a function that returnsTrue
orFalse
The
TemplateForm
must have acontent_panel
. It is often could to refer toTemplateForm``s with the suffix ``Router
e.g.MainRouter
,AdminRotuer
. There are two callbacks available to aTemplateForm
.- on_navigation(self, url_hash, url_patter, url_dict, unload_form)
The
on_navigation
method, when added to yourTemplateForm
, will be called whenever theurl_hash
is changed. It’s a good place to adjust the look of yourTemplateForm
if theurl_hash
changes. e.g. the selected link in the sidebar. Theunload_form
is possibleNone
if 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_load
is 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.route
decorator should be called with arguments that determine the shape of theurl_hash
. Theurl_pattern
determines the string immediately after the#
. Theurl_keys
determine 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 notemplate
is set then this form can be added to any template.The routing module adds certain parameters to a
Route Form
and supports abefore_unload
callback.- url_hash
The current
url_hash
. Theurl_hash
includes the query. See Introduction for examples.
- url_pattern
The
url_hash
without 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_unload
method is added it will be called whenever the form currently in thecontent_panel
is 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.route
all 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_route
decorator 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
,priority
andcondition
arguments.path
can be a string or iterable of strings.priority
should be an integer - the higher the value the higher the priority.conditon
should beNone
or a callable that returns aTrue
orFalse
.
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_form
decorator is optional and can be added above a form that will be displayed if theurl_hash
does not refer to any knownRoute Form
.
Exception
Usually called inside the
on_navigation
callback. 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_hash
and template conditions. Callingopen_form()
on aTemplateForm
will implicitly callrouting.launch()
. Untilrouting.launch()
is called anvil components will not be loaded when theurl_hash
is changed. This allows you to set theurl_hash
in startup logic before any navigation is attempted. Similarly when aTemplateForm
is loaded any routing is delayed until after theTemplateForm
has 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_hash
and 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_url
is set toTrue
. Then the navigation will happen “in place” rather than as a new history item.If
set_in_history
is set toFalse
the URL will not be added to the browser’s history stack.If
redirect
is set toFalse
then you do not want to navigate away from the current form.if
load_from_cache
is set toFalse
then 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.alert
then alerts will not close when the user navigates. This is probably not what you want. When usingrouting.alert
any alert that isdismissible
will close when the user navigates. Any non-dismissible alert will block the navigation.You may want to do
import anvil; anvil.alert = routing.alert
as the first line in a startup module to overrideanvil.alert
across your app.
- routing.get_url_components(url_hash=None)
Returns a 3 tuple of the
url_hash
,url_pattern
andurl_dict
. If theurl_hash
is None it will return the components based on the currenturl_hash
of 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_hash
without 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_hash
from therouting
module’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
routing
module. 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 = True
the 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_keys
or 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_hash
changeson_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_hash
s 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).