A virtual routing table in (almost) vanilla javacsript with two level of routing in 70 lines

The code



Since the code is small, before detailing it:
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<style>
.content { visibility: none }
</style>
<script>
$(document).ready(() => {
    var query = new URLSearchParams(window.location.search )
    route = query.get("route")
    anchor = document.URL.match(/#[^\?]+/)

    $(".router").each( (i,e) => {
        var query = new URLSearchParams( e.attributes.href.value )
        $(e).addClass(query.get("route"))
    })
    $(".selector").each((i,e) => $(e).addClass($(e).attr("href").substr(1)))
    $(".selector").click( function(e) {
        e.preventDefault()
        history.pushState({} ,"", "" + $(this).attr("href"));
        anchor = $(this).attr("href").replace(/\?.*/,"");
        ({
            "#subroute1" : console.log,
            "#subroute2":  (e) => alert(e),
            "#subroute3" : console.log,
            "#subroute4":  (e) => alert(e)  })[anchor](anchor)
        console.log("subroute called:" + anchor)
        return true
    })
    $(".router").click( (e) => {
        e.preventDefault()
        var href=$(e.target)[0].attributes.href
        url = href.value
        if (anchor)
            url += anchor
        history.pushState({}, null, url)
        var query = new URLSearchParams( href.value )
        var route = query.get("route");
        ({
            "route1": () => { $(".content").hide(); $("." + route).show() },
            "route2": () => { $(".content").hide(); $("." + route).show() },
            "route3": () => { $(".content").hide(); $("." + route).show() },
        })[route]()
        console.log("route called:" + route)
        return true
    })
    if (route == undefined ) {
        console.log("default route")
        $(".router.route1").click()
    } else {
        console.log('dealing with: ' +route +':'+ anchor)
        $(".router" + "." + route).click()
    }
    anchor = document.URL.match(/#[^\?]+/)
    if (anchor) {
        console.log("anchor detected in URL:" + anchor[0] + "route:" + route)
        $("." + route +"  ." + anchor[0].substr(1)).click()
        console.log("sub route called:" +anchor[0])
    }
    console.log("ready")
})
</script>
<ul>
    <li><a class="router" href=?route=route1>route1</a></li>
    <ul class="content route1">
        <li><a class="selector" href=#subroute1 >sub menu 1</a></li>
        <li><a class="selector" href=#subroute2 >sub menu 2</a></li>
    </ul>
    <li><a class="router" href=?route=route2>route2</a></li>
    <ul class="content route2" >
        <li><a class=selector href=#subroute3 >sub menu 3</a></li>
        <li><a class=selector href=#subroute4 >sub menu 4</a></li>
    </ul>
    <li><a class="router" href=?route=route3>route3</a></li>
</ul>
<div class='content route1'>route 1</div>
<div class='content route2'>route 2</div>
<div class='content route3'>no subroute</div>



The need



In a project named yahi, for log parsing I am putting in a single HTML page all the data, and the views to avoid using a dynamic server, so I have to do the routing for calling the right javascript either :
  • when clicking on the a element I chose for displaying data
  • when loading an URL so that not only pages are available but also sharable.
So came the need for a level 2 router. Route layer 1 are gonna be stored in the GET key route, and subroute are gonna be called by clicking on the anchor of their names.



The how of the code



We being with the HTML:
<ul>
    <li><a class="router" href=?route=route1>route1</a></li>
    <ul class="content route1">
        <li><a class="selector" href=#subroute1 >sub menu 1</a></li>
        <li><a class="selector" href=#subroute2 >sub menu 2</a></li>
    </ul>
    <li><a class="router" href=?route=route2>route2</a></li>
    <ul class="content route2" >
        <li><a class=selector href=#subroute3 >sub menu 3</a></li>
        <li><a class=selector href=#subroute4 >sub menu 4</a></li>
    </ul>
    <li><a class="router" href=?route=route3>route3</a></li>
</ul>
<div class='content route1'>route 1</div>
<div class='content route2'>route 2</div>
<div class='content route3'>no subroute</div>
We tie the router to the HTML router class and the route is on one place the href value of the a element.
We tie the sub route to the HTML selector class and the route is on one place the href value of the a element.

When the document is loaded ($(document).ready) :
    var query = new URLSearchParams(window.location.search )
    route = query.get("route")
    anchor = document.URL.match(/#[^\?]+/)
we get the route from URL GET parameters, and anchor from the document.URL

    $(".router").each( (i,e) => {
        var query = new URLSearchParams( e.attributes.href.value )
        $(e).addClass(query.get("route"))
    })
    $(".selector").each((i,e) => $(e).addClass($(e).attr("href").substr(1)))
We tag the links of class selector and route with their respective subroute and route as a class for being to later able to target them with their CSS selector.

    $(".selector").click( function(e) {
        e.preventDefault()
        history.pushState({} ,"", "" + $(this).attr("href"));
        anchor = $(this).attr("href").replace(/\?.*/,"");
        ({
            "#subroute1" : console.log,
            "#subroute2":  (e) => alert(e),
            "#subroute3" : console.log,
            "#subroute4":  (e) => alert(e)  })[anchor](anchor)
        console.log("subroute called:" + anchor)
        return true
    })
We are gonna emulate a legitimate click on an a element, so first we disable it.
Then, we push in the history like a legitimate click does the URL that has been click making sure our subroute is the one specified.
I do cleanup that may be dead code of the anchor according to garbage that could be there.

and then I call with a dispatch table the subroute available.

And for eviting hogs, I don't know why, I have to return something.

    $(".router").click( (e) => {
        e.preventDefault()
        var href=$(e.target)[0].attributes.href
        url = href.value
        if (anchor)
            url += anchor
        history.pushState({}, null, url)
        var query = new URLSearchParams( href.value )
        var route = query.get("route");
        ({
            "route1": () => { $(".content").hide(); $("." + route).show() },
            "route2": () => { $(".content").hide(); $("." + route).show() },
            "route3": () => { $(".content").hide(); $("." + route).show() },
        })[route]()
        console.log("route called:" + route)
        return true
    })
We are gonna emulate a legitimate click on an a element, so first we disable it.
We get the wanted url from looking at the source of truth : our own href.
anchor variable is a reference to the one parsed from the URL. If it exists we append it to the url that we push in the history.
We get the truth of the wanted route by parsing the href value of clicked element, and with a dispatch table we call the route. Then, we push in the history like a legitimate click does the URL that has been click making sure our subroute is the one specified.
I do cleanup that may be dead code of the anchor according to garbage that could be there.

and then I call with a dispatch table the subroute available.

And for eviting hogs, I don't know why, I have to return something.

    if (route == undefined ) {
        console.log("default route")
        $(".router.route1").click()
    } else {
        console.log('dealing with: ' +route +':'+ anchor)
        $(".router" + "." + route).click()
    }
Here we look that route called in the URL, if none is specified we decide a default route

    anchor = document.URL.match(/#[^\?]+/)
    if (anchor) {
        console.log("anchor detected in URL:" + anchor[0] + "route:" + route)
        $("." + route +"  ." + anchor[0].substr(1)).click()
        console.log("sub route called:" +anchor[0])
    }
    console.log("ready")
Just in case someone JUST modified the URL in a click action we check the information from the document.URL. And, if so we emulate a click on the subroute if an only if it is in a div of the correct route so people cannot mess by specifying a wrong route for a subroute in the URL.

And that's the all in one page with the trick.

No comments: