Javascript Single Page Application routing & moving programmatically

Viewed 247

So I have a Single Page JavaScript web app that I've built from a tutorial HERE.

It's working well - I've integrated a few changes that people had in the Issues and Pull Requests on the GitHub link as well. My links are also working well. If you click on a <a href> link, it redirects without reloading the whole page. AKA It's working as expected!

My problem comes when I want to use Javascript to change the page location. IE: As a result of a function/result of API call/something else, I want to be able to force the page to navigate to a different page without reloading the whole screen.

I'm hoping that I have all the required code below for someone to be able to help... Fingers crossed! Please let me know if I've missed something that you need to help.

My thought process goes along the lines of:

  • The singlePageApp.js adds an event listener to the <a href='' data-link> tags that calls navigateTo
  • I just need to be able to somehow call that navigateTo function from somewhere else
  • I don't fully understand the code on that page enough to be able to make it available to the rest of the app because I can't just call navigateTo('/whatever'); from any script. It results in a function not defined error.

What have I tried? A lot - and I've forgotten what, but a few things

  • history.pushState(null, null, url); from a script
  • navigateTo('/whatever'); from a script
  • window.location.pathname = '/whatever'; router() <- This just results in a router not defined message

Other notes

  • I'm not using react or other frameworks. Straight Javascript.
  • I have jquery installed if it helps - Only reason is as a dependance on dataTables, but I'm not using it anywhere else.

Thank you VERY much in advance

Working links in a NAV bar

<a class="dropdown-item" href="#" data-link>List all services</a>
<a class="dropdown-item" href="/service/vm365/step1" data-link>Provision Service</a>

index.html

<script type="module" src="/static/js/singlePageApp.js"></script>

singlePageApp.js

import ServiceVM365Step1 from "./views/ServiceVM365Step1.js";
import ServiceVM365Step2 from "./views/ServiceVM365Step2.js";
import ServiceVM365Step3 from "./views/ServiceVM365Step3.js";
import ServiceVM365Step4 from "./views/ServiceVM365Step4.js";
import Dashboard from "./views/Dashboard.js";

const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");

const getParams = match => {
    const values = match.result.slice(1);
    const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);

    return Object.fromEntries(keys.map((key, i) => {
        return [key, values[i]];
    }));
};

const navigateTo = url => {
    history.pushState(null, null, url);
    router();
};

const router = async () => {
    const routes = [
        { path: "/", view: Dashboard },
        { path: "/service/vm365/step1", view: ServiceVM365Step1 },
        { path: "/service/vm365/step2", view: ServiceVM365Step2 },
        { path: "/service/vm365/step3", view: ServiceVM365Step3 },
        { path: "/service/vm365/step4", view: ServiceVM365Step4 }
    ];

    // Test each route for potential match
    const potentialMatches = routes.map(route => {

        return {
            route: route,
            result: location.pathname.match(pathToRegex(route.path))

        };
    });


    var match
    potentialMatches.forEach(entry => {

        if (entry.result != null) {

            match = (match === undefined || entry.route.path.length > match.route.path.length) ? entry : match
        }

    })



    if (!match) {
        match = {
            route: routes[0],
            result: [location.pathname]
        };
    }

    const view = new match.route.view(getParams(match));

    document.querySelector("#content").innerHTML = await view.getHtml();
};

window.addEventListener("popstate", router);

document.addEventListener("DOMContentLoaded", () => {
    document.body.addEventListener("click", e => {
        if (e.target.matches("[data-link]")) {
            e.preventDefault();
            navigateTo(e.target.href);
            
        }
    });

    router();
});

//////////////////////////////////////////////////////////////////
//// CODE BELOW WAS ME TRYING TO GET IT WORKING... It doesn't ////

var oldHref = document.location.href;

window.addEventListener("load", () => {
    var bodyList = document.querySelector("body")

    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (oldHref != document.location.href) {
                oldHref = document.location.href;
                //////////////////////////////////////////                
                //NOTE: This does get called when a <a href> link is clicked, but not when ONLY history.pushState is used to change the path from a script
                console.error("CALLED NOW")
                router();
            }
        });
    });

    var config = {
        childList: true,
        subtree: true
    };

    observer.observe(bodyList, config);
});
0 Answers
Related