/ client_code / routing / _navigation.py
_navigation.py
  1  # SPDX-License-Identifier: MIT
  2  #
  3  # Copyright (c) 2021 The Anvil Extras project team members listed at
  4  # https://github.com/anvilistas/anvil-extras/graphs/contributors
  5  #
  6  # This software is published at https://github.com/anvilistas/anvil-extras
  7  
  8  from functools import wraps
  9  
 10  from anvil.js.window import history, location, window
 11  
 12  from . import _router
 13  
 14  __version__ = "3.1.0"
 15  
 16  # re-initialise the state object which was overridden on load or this is a new session
 17  state = history.state or {"url": location.hash, "pos": 0}
 18  if "url" not in state:
 19      state = {"url": location.hash, "pos": 0}
 20  history.replaceState(state, "", state["url"])
 21  
 22  # undo and pos are used for unload behavior
 23  current = {"undo": 0, "pos": state["pos"]}
 24  
 25  # Form Unload Behaviour - here we prevent the user from navigating away from the current form
 26  # while we wait for the unload function to complete
 27  
 28  undoing = False
 29  waiting = False
 30  
 31  
 32  # when the window back or forward button is pressed onPopState is triggered
 33  # a popstate is also triggered from a change in the URL in this case the window.history.state will be None
 34  def onPopState(e):
 35      global undoing, waiting
 36      if undoing:
 37          undoing = False
 38          current["pos"] = history.state["pos"]
 39          return
 40      elif waiting:
 41          return preventUnloadPopState(e)
 42  
 43      state = e.state
 44      if state:  # then we're loading from back forward navigation
 45          current["undo"] = current["pos"] - state["pos"]
 46          current["pos"] = state["pos"]
 47      else:
 48          current["undo"] = -1
 49          current["pos"] += 1
 50          state = {"url": location.hash, "pos": current["pos"]}
 51  
 52      history.replaceState(state, "", state["url"])
 53      # we always favour the state['url'] over location.hash
 54      # since we allow (replace_current_url=True, set_in_history=False)
 55  
 56      _router.navigate()
 57  
 58  
 59  window.onpopstate = onPopState
 60  
 61  
 62  def stopUnload():
 63      global undoing
 64      undoing = True
 65      history.go(current["undo"])
 66  
 67  
 68  def preventUnloadPopState(e):
 69      global undoing
 70      e.preventDefault()
 71      state = e.state
 72      if state:
 73          undoing = True
 74          history.go(current["pos"] - state["pos"])  # reverse the navigation
 75      else:
 76          # the user is determined to navigate away and has changed the url manually so let them!
 77          # Not letting them will break the app...
 78          current["pos"] += 1
 79          state = {"url": location.hash, "pos": current["pos"]}
 80          history.replaceState(state, "")
 81          window.onbeforeunload = None
 82          location.reload()
 83  
 84  
 85  class PreventUnloading:
 86      def __enter__(self):
 87          global waiting
 88          waiting = True
 89  
 90      def __exit__(self, *args):
 91          global waiting
 92          waiting = False
 93  
 94  
 95  def ensure_hash(f):
 96      @wraps(f)
 97      def hash_wrapper(url):
 98          if not url.startswith("#"):
 99              url = "#" + url
100          return f(url)
101  
102      return hash_wrapper
103  
104  
105  @ensure_hash
106  def pushState(url):
107      # set_in_history = True, replace_current_url=False
108      current["pos"] += 1
109      current["undo"] = -1
110      state = {"url": url, "pos": current["pos"]}
111      history.pushState(state, "", url)
112  
113  
114  @ensure_hash
115  def replaceState(url):
116      # set_in_history=True, replace_current_url=True
117      current["undo"] = 0
118      state = {"url": url, "pos": history.state["pos"]}
119      history.replaceState(state, "", url)
120  
121  
122  @ensure_hash
123  def replaceUrlNotState(url):
124      # set_in_history=False, replace_current_url=True
125      current["undo"] = 0
126      history.replaceState(history.state, "", url)