How to enable use of the forward button in my vanilla typescript single page app (code examples included)

29 views Asked by At

I am building a single page application using vanilla typescript and golang. I am having trouble managing my history properly.

To be clear, here is how the application functions on initial page load.

  1. You make a request to the go http server.
  2. You get back a blank HTML template with a script tag at the bottom.
  3. The browser makes a request to the server for the .js bundle.
  4. Javascript bundle is retrieved and then takes the browser's window.location.pathname and runs an associated handler function
  5. The handler executes middleware.
  6. The handler renders the view.
  7. Request complete.

From here, the client-side javascript handles routing through different views via a redirect function.

Fun, now we get into some code.

Here is the global session I use to track the users history and potential state values. It also contains the apps router.

export type Session = {
    Router: Map<string, () => void>,
    History: string[],
    State: Map<string, any>,
}

export let session = {
    Router: new Map<string, () => void>(),
    History: new Array<string>(),
    State: new Map<string, any>(),
}

Routes are simply registered in the apps root-level index.ts

import { enableHistory, preventForwardNavigation } from "./client/events"
import { session } from "./client/global"
import { handlerHome, handlerLogin } from "./client/handlers"

enableHistory()
preventForwardNavigation()

session.Router.set("/", handlerHome)
session.Router.set("/login", handlerLogin)

let handler = session.Router.get(window.location.pathname)
if (handler) {
    handler()
} else {
    console.log("404")
}

Let's take a closer look at the enableHistory() and preventForwardNavigation() functions.

export const enableHistory = () => {
    window.addEventListener('popstate', (e: PopStateEvent) => {
        let previousURL = session.History[session.History.length - 2]
        session.History = session.History.slice(0, session.History.length - 2)
        if (previousURL) {
            let handler = session.Router.get(previousURL)
            if (handler) {
                window.history.pushState({}, "", previousURL)
                handler()
            }
        } else {
            history.back()
        }
    })
}

export const preventForwardNavigation = () => {
    if (window.history && window.history.forward) {
        window.history.forward = function() {
            alert("Forward navigation is disabled.");
        };
    }   
}

Okay, so as an overview, the enableHistory() grabs the next to last element off the session.History stack (which represents the previous path loaded) and then calls the appropriate handler function found in session.Router.

Lets take a look at a handler and then a view so we can see what it looks like when history is pushed onto session.History

Here is a handler which is called when a user hits "/":

export const handlerHome = () => {
    document.title = "CFA Suite - Home"
    mwRunner([mwInit, () => {
        render(ViewHome())
    }, mwTele, mwLinks, mwLog])
}

The handler calls middleware and then makes a call to ViewHome() which ultimately generates the HTML the user sees and interacts with.

Okay, now we are closing in on the problem. Lets take a look at mwLinks, which is a middleware which modifies the way links function in the application.

export const mwLinks = (): void => {
    let links = document.querySelectorAll('a')
    links.forEach(link => {
        link.addEventListener('click', (e: Event) => {
            e.preventDefault()
            let href = link.getAttribute('href')
            if (href) {
                redirect(href)
            }
        })
    })
}

As you can see, all links in the application make a call to redirect() when they are clicked. Here the function redirect:

export function redirect(fullPath: string) {
    window.history.pushState({}, "", fullPath)
    let handler = session.Router.get(getBasePath(fullPath))
    if (handler) {
        handler()
    } else {
        console.log("404")
    }
}

Okay, so as you can see, history is really managed 3 places in the application. On initial page load, we make calls to enableHistory() and preventForwardNavigation() and then when a link is clicked, we make called to redirect() which pushes state to window.history and pushes a path on session.History.

So to be clear, history in the app works. But I have two problems:

  1. The forward button does not work properly. That is why I disabled it. And I am not 100% sure why it doesn't work properly.

  2. When I right click my back button (like the actual back button in the browser) I notice my history stack continues to grow after clicking the back button.

How can I resolve this problem and make my forward button work properly while having an accurate history stack within the actual browser history stack (not session.History).

I have tried extensively to solve this problem and has been the hardest issue in my entire project.

Thank you so much for your time, I appreciate you!

0

There are 0 answers