How to create a vanilla JS routing for SPA?

北慕城南 提交于 2021-01-27 07:54:44

问题


I'm creating a web app with NO frameworks/tools/libraries. Why? It doesn't matter.

All Vanilla JS. I'm doing it more of 'React' style. I'm wondering, how can I call the view that are in my views/pages/dashboard.js and display that when the user click the dashboard nav link?

Perhaps the sub-nav items. What if the user is in the github folder on profile, how would I display that in the url as well?

How can I create a routing for this? I've read articles an watched YT videos but can't seem to implement it here.

Here's the GitHUb repo: https://github.com/AurelianSpodarec/JS_GitHub_Replica/tree/master/src/js

All the articles out there.

I'm pretty new so trying to figure this out.

This is what I've tried

document.addEventListener("DOMContentLoaded", function() {
    var Router = function (name, routes) {
        return {
            name: name,
            routes: routes
        }
    };
    var view = document.getElementsByClassName('main-container');
    var myRouter = new Router('myRouter', [
        {
            path: '/',
            name: "Dahsboard"
        },
        {
            path: '/todo',
            name: "To-Do"
        },
        {
            path: '/calendar',
            name: "Calendar"
        }
    ]);
    var currentPath = window.location.pathname;
    if (currentPath === '/') {
        view.innerHTML = "You are on the Dashboard";
        console.log(view);
    } else {
        view.innerHTML = "you are not";
    }
});

When the user clicks an item on the nav, show load the view. So this is the navbar: https://codepen.io/Aurelian/pen/EGJvZW and I want to load the right view and change the url.


回答1:


As i said in the comments, listening for popstate and using the hashtag (#) method is the easiest way to do routing in JS.

This is the most bare bones for a router:

//App area
var appArea = document.body.appendChild(document.createElement("div"));
//Registered routes
var routes = [
    {
        url: '', callback: function () {
            appArea.innerHTML = "<h1>Home</h1><a href=\"#todo\">To-Do</a><br/><a href=\"#calendar\">Calendar</a>";
        }
    }
];
//Routing function
function Routing() {
    var hash = window.location.hash.substr(1).replace(/\//ig, '/');
    //Default route is first registered route
    var route = routes[0];
    //Find matching route
    for (var index = 0; index < routes.length; index++) {
        var testRoute = routes[index];
        if (hash == testRoute.url) {
            route = testRoute;
        }
    }
    //Fire route
    route.callback();
}
//Listener
window.addEventListener('popstate', Routing);
//Initial call
setTimeout(Routing, 0);
//Add other routes
routes.push({ url: "todo", callback: function () { appArea.innerHTML = "<h1>To-Do</h1><a href=\"#\">Home</a><br/><a href=\"#calendar\">Calendar</a>"; } });
routes.push({ url: "calendar", callback: function () { appArea.innerHTML = "<h1>Calendar</h1><a href=\"#\">Home</a></br><a href=\"#todo\">To-Do</a>"; } });

Now in any real context you would want reusable DOM elements and scope-unload functions so here is how the above should probably look:

// ## Class ## //
var Router = /** @class */ (function () {
    function Router() {
    }
    //Initializer function. Call this to change listening for window changes.
    Router.init = function () {
        //Remove previous event listener if set
        if (this.listener !== null) {
            window.removeEventListener('popstate', this.listener);
            this.listener = null;
        }
        //Set new listener for "popstate"
        this.listener = window.addEventListener('popstate', function () {
            //Callback to Route checker on window state change
            this.checkRoute.call(this);
        }.bind(this));
        //Call initial routing as soon as thread is available
        setTimeout(function () {
            this.checkRoute.call(this);
        }.bind(this), 0);
        return this;
    };
    //Adding a route to the list
    Router.addRoute = function (name, url, cb) {
        var route = this.routes.find(function (r) { return r.name === name; });
        url = url.replace(/\//ig, '/');
        if (route === void 0) {
            this.routes.push({
                callback: cb,
                name: name.toString().toLowerCase(),
                url: url
            });
        }
        else {
            route.callback = cb;
            route.url = url;
        }
        return this;
    };
    //Adding multiple routes to list
    Router.addRoutes = function (routes) {
        var _this = this;
        if (routes === void 0) { routes = []; }
        routes
            .forEach(function (route) {
            _this.addRoute(route.name, route.url, route.callback);
        });
        return this;
    };
    //Removing a route from the list by route name
    Router.removeRoute = function (name) {
        name = name.toString().toLowerCase();
        this.routes = this.routes
            .filter(function (route) {
            return route.name != name;
        });
        return this;
    };
    //Check which route to activate
    Router.checkRoute = function () {
        //Get hash
        var hash = window.location.hash.substr(1).replace(/\//ig, '/');
        //Default to first registered route. This should probably be your 404 page.
        var route = this.routes[0];
        //Check each route
        for (var routeIndex = 0; routeIndex < this.routes.length; routeIndex++) {
            var routeToTest = this.routes[routeIndex];
            if (routeToTest.url == hash) {
                route = routeToTest;
                break;
            }
        }
        //Run all destroy tasks
        this.scopeDestroyTasks
            .forEach(function (task) {
            task();
        });
        //Reset destroy task list
        this.scopeDestroyTasks = [];
        //Fire route callback
        route.callback.call(window);
    };
    //Register scope destroy tasks
    Router.onScopeDestroy = function (cb) {
        this.scopeDestroyTasks.push(cb);
        return this;
    };
    //Tasks to perform when view changes
    Router.scopeDestroyTasks = [];
    //Registered Routes
    Router.routes = [];
    //Listener handle for window events
    Router.listener = null;
    Router.scopeDestroyTaskID = 0;
    return Router;
}());
// ## Implementation ## //
//Router area
var appArea = document.body.appendChild(document.createElement("div"));
//Start router when content is loaded
document.addEventListener("DOMContentLoaded", function () {
    Router.init();
});
//Add dashboard route
Router.addRoute("dashboard", "", (function dashboardController() {
    //Scope specific elements
    var header = document.createElement("h1");
    header.textContent = "Dashboard";
    //Return initializer function
    return function initialize() {
        //Apply route
        appArea.appendChild(header);
        //Destroy elements on exit
        Router.onScopeDestroy(dashboardExitController);
    };
    //Unloading function
    function dashboardExitController() {
        appArea.removeChild(header);
    }
})());
//Add dashboard route
Router.addRoute("dashboard", "", (function dashboardController() {
    //Scope specific elements
    var header = document.createElement("h1");
    header.textContent = "Dashboard";
    var links = document.createElement("ol");
    links.innerHTML = "<li><a href=\"#todo\">To-Do</a></li><li><a href=\"#calendar\">Calendar</a></li>";
    //Return initializer function
    return function initialize() {
        //Apply route
        appArea.appendChild(header);
        appArea.appendChild(links);
        //Destroy elements on exit
        Router.onScopeDestroy(dashboardExitController);
    };
    //Unloading function
    function dashboardExitController() {
        appArea.removeChild(header);
        appArea.removeChild(links);
    }
})());
//Add other routes
Router.addRoutes([
    {
        name: "todo",
        url: "todo",
        callback: (function todoController() {
            //Scope specific elements
            var header = document.createElement("h1");
            header.textContent = "To-do";
            var links = document.createElement("ol");
            links.innerHTML = "<li><a href=\"#\">Dashboard</a></li><li><a href=\"#calendar\">Calendar</a></li>";
            //Return initializer function
            return function initialize() {
                //Apply route
                appArea.appendChild(header);
                appArea.appendChild(links);
                //Destroy elements on exit
                Router.onScopeDestroy(todoExitController);
            };
            //Unloading function
            function todoExitController() {
                appArea.removeChild(header);
                appArea.removeChild(links);
            }
        })()
    },
    {
        name: "calendar",
        url: "calendar",
        callback: (function calendarController() {
            //Scope specific elements
            var header = document.createElement("h1");
            header.textContent = "Calendar";
            var links = document.createElement("ol");
            links.innerHTML = "<li><a href=\"#\">Dashboard</a></li><li><a href=\"#todo\">To-Do</a></li>";
            //Return initializer function
            return function initialize() {
                //Apply route
                appArea.appendChild(header);
                appArea.appendChild(links);
                //Destroy elements on exit
                Router.onScopeDestroy(calendarExitController);
            };
            //Unloading function
            function calendarExitController() {
                appArea.removeChild(header);
                appArea.removeChild(links);
            }
        })()
    }
]);



回答2:


You can use navigo or have a brainstorm by seeing what other people are doing.

Another option - away from React/Angular - is to use sapper and you can take a truly revealing comparison of frameworks from there.

In my opinion a router should be generic and not only show/hide existing parts of an application but also send requests to server and receive ajax responses for page inclusion; in that way a request for /eshop/phones/samsung should make a ajax request and include html code at a node sth like <div id="eshop">. That way we need:

1) a url handler that blocks all clicks and reformats browser path and

2) a callback, what to do with that

and that's all!

SEO is achieved by mapping the exact same urls with actual cached pages; such urls are a subset of what the router handles as some - like the above - result in dynamically constructed pages.

From the point of view of a network bot dynamically constructed pages require js code to run, the router + supporting code (the bot can be capable of running js but even then the path is just one of thousands built from parameters converted to paths so no usable for indexing but it should be usable for bookmarking!).

Now you have a SEO + bookmarking feature on top of a router sth difficult to get from say Angular (that's so complicated that when one app is finished you have no clue how on earth you would reuse it to another project!).

At the end of day such a router mirrors a server one for the urls of cached pages + urls for dynamically constructed page parts, is minimal and combines both worlds: SPAs and server rendered pages.



来源:https://stackoverflow.com/questions/54231533/how-to-create-a-vanilla-js-routing-for-spa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!