How to close alerts when navigating away from current page with HashRouting?

An app that uses HashRouting (or the new anvil_extras.routing) to manage the navigation and shows an alert, will navigate to the previous page without closing the alert when the user clicks on the browser’s back button.

A workaround that I am using is to wrap all the alerts like this:

    self.alert_is_on = True
    result = alert('Hello!')
    self.alert_is_on = False

and prevent the routing module from navigating away with this:

  def before_unload(self):
    if self.alert_is_on:
      return True

Is there a better way to automatically close the alerts when the browser navigates away?

If not, how about adding a router.alert() function that takes care of it?

1 Like

If the alert content is a component - and you have access to that component - you can do component.raise_event('x-close-alert').

So something like

  def __init__(self):
    self.alert_component = None
    ...

  def before_unload(self):
    if self.alert_component is not None:
      self.alert_component.raise_event('x-close-alert')

(can also put that in the form’s hide event rather than before_unload method)

It would work, but I don’t like to manage self.alert_component every time I use alert(). Sometimes the alert is shown when I click on a form in a repeating panel, which is not even the form that manages the navigation.

I made these little helper functions in a module and it seems to be working fine:

from anvil import alert

_alert_is_on = False

def alert2(content, title='', buttons=None, large=False, dismissible=True, role=None):
  global _alert_is_on
  _alert_is_on = True
  value = alert(content, title=title, buttons=buttons, large=large, dismissible=dismissible, role=role)
  _alert_is_on = False
  return value

def alert_is_on():
  return _alert_is_on

Then in the forms I use alert2() instead of alert() and I add this:

  def before_unload(self):
    if alert_is_on():
      return True

This makes dismissible alerts not dismissible by navigating away from the page, but I can live with that.

Perhaps the routing module could define the same routing.alert2() function and execute the check before unloading the form without requiring the explicit definition of the before_unload in each form? :wink:

Here’s a decorator I created from your code that minimizes the changes needed to block the back button when an alert is being shown (only the decorator needs added, no change to how you call alert or confirm). This has been minimally tested, and I’m sure there are improvements to be made.

This works for alerts inside repeating panel templates, as long as the containing form has the decorator.

# This module is designed to allow an app using hash routing to 
# prevent navigation when an alert is being shown.  Currently,
# when an Anvil alert is being shown, the user can still press
# the back button on their browser.  The Anvil app displays the
# correct previous form, but the alert remains shown.  
#
# Using the decorator provided here, the back button will not 
# move to the previous form when an alert is being shown.
#
# The import of this module must happen after the import of anvil
# itself

def alert_protection(cls):
  def _before_unload(self):
    global _alert_is_on
    
    if _alert_is_on:
      return True
    
  setattr(cls, 'before_unload', _before_unload)
  
  global _alert_is_on
  _alert_is_on = False

  return cls
  
def alert(content, **kwargs):
  import anvil
  global _alert_is_on
  
  _alert_is_on = True
  value = anvil.alert(content, **kwargs)  
  _alert_is_on = False
  return value

def confirm(content, **kwargs):
  import anvil
  global _alert_is_on
  
  _alert_is_on = True
  value = anvil.confirm(content, **kwargs)  
  _alert_is_on = False
  return value
1 Like

Mmmh… have you tried this with a form that defines its own before_unload?

I’m guessing the form class is defined then fed to the decorator, so you are replacing the one defined in the form.

Do you think it is possible to check whether the before_unload is already defined, and if it is, it decorate it with a function that first does our check, if it doesn’t it defines it as you did?

Good catch! This version maintains the existing before_unload and calls it if there’s no alert showing:

# This module is designed to allow an app using hash routing to 
# prevent navigation when an alert is being shown.  Currently,
# when an Anvil alert is being shown, the user can still press
# the back button on their browser.  The Anvil app displays the
# correct previous form, but the alert remains shown.  
#
# Using the decorator provided here, the back button will not 
# move to the previous form when an alert is being shown.
# 
# If you are calling an alert from inside a repeating row 
# template, you must import this module so its versions
# of alert and confirm are used.
#
# The import of this module must happen after the import of anvil
# itself

def alert_protection(cls):
  def _before_unload(self):
    global _alert_is_on
    
    if _alert_is_on:
      return True
    
    if hasattr(cls, 'old_before_unload'):
      return self.old_before_unload()
    
  if hasattr(cls, 'before_unload'):
    setattr(cls, 'old_before_unload', getattr(cls, 'before_unload'))
    
  setattr(cls, 'before_unload', _before_unload)
  
  global _alert_is_on
  _alert_is_on = False

  return cls
  
def alert(content, **kwargs):
  import anvil
  global _alert_is_on
  
  _alert_is_on = True
  value = anvil.alert(content, **kwargs)  
  _alert_is_on = False
  return value

def confirm(content, **kwargs):
  import anvil
  global _alert_is_on
  
  _alert_is_on = True
  value = anvil.confirm(content, **kwargs)  
  _alert_is_on = False
  return value
2 Likes

And the most recent version of routing inside Anvil Extras makes all of the above unnecessary. Routing will automatically close dismissible alerts when navigating away from the current page, and will block navigation when a non-dismissible alert is showing.

5 Likes