Exploring URLs client side

URLs are one of the basic building blocks of the Web, and in this article we’re going to learn some techniques to explore them client-side using Dojo.

window.location

The window.location object describes the URL of the current page, and has a number of attributes that Javascript code can examine to figure out where we are and how we got there. Lets see what the window.location object has to say about the following URL:
https://www.example.com:8000/search?q=dojo&other=1#test

Property Description Example
hash the part of the URL that follows the # symbol, including the # symbol. #test
host the host name and port number. www.example.com:8000
hostname the host name (without the port number). www.example.com
href the entire URL. https://www.example.com:8000/search?q=dojo&other=1#test
pathname the path (relative to the host). /search
port the port number of the URL. 8000
protocol the protocol of the URL. https:
search the part of the URL that follows the ? symbol, including the ? symbol. ?q=dojo&other=1
Modified from the window.location docs at Mozilla Developer Center

The Query String

Server-side technologies like PHP or Django often use HTTP GET parameters to perform specific actions. Lets take a Google search URL for the text “dojo”: https://www.google.co.nz/search?hl=en&q=dojo&btnG=Search&meta=

The GET parameters in that URL are q, hl, btnG, and meta, and they form the query string (?hl=en&q=dojo&btnG=Search&meta=). Now, obviously the server does something useful with those parameters (like search for dojo) but we can also access them client-side via the dojo.queryToObject() function:

var queryParams = dojo.queryToObject(window.location.search.slice(1));
// we use slice(1) to strip the leading "?"
// queryParams is now an object with a key for each GET parameter
// eg. { q:"dojo", hl:"en", btnG="Search", meta:"" }

Once we have this function we can do some useful things, particularly with static HTML pages. Lets say we had a set of 3 tabs in a page. Rather than having a PHP script just to handle the menial task of setting the current tab, we can pick it up client-side from the query string.

See the first example.

So, url_1.html?tab=2 will select the third tab (with index 2)

dojo.addOnLoad(function() {
    // see if we have a ?tab=N parameter and set the current tab based on it
    if (window.location.search) {
        // convert our query string into an object (use slice to strip the leading "?")
        var queryParams = dojo.queryToObject(window.location.search.slice(1));
        // get the tab index: parseInt will convert undefined/rubbish to NaN
        var tabIndex = parseInt(queryParams["tab"]);
 
        var tabStrip = dijit.byId("myTabs");
        if (isNaN(tabIndex) || tabIndex >= tabStrip.getChildren().length) {
            // default to the first tab
            tabIndex = 0; 
        }
        // set the current tab
        tabStrip.selectChild(tabStrip.getChildren()[tabIndex]);
    }
});

We can then pass around URLs (Email, Twitter, IM, blogs) that point to the exact tab we want. And if we have links on the page that swap to a specific tab we can add a little bit of progressive enhancement…

// enhance our inter-tab links to prevent page reloading
dojo.query("a.tabLink").connect("onclick", function(e) {
    // we store the target tab index in the "tag" attribute
    var targetIndex = parseInt(e.target.getAttribute("tag"));
    var newTab = dijit.byId("myTabs").getChildren()[targetIndex];
    if (newTab) {
        // select the new tab
        dijit.byId("myTabs").selectChild(newTab);
        // cancel the event (prevent the browser from following the link)
        dojo.stopEvent(e);
    }
});
See more <a class="tabLink" href="?tab=1" tag="1">exciting cookie flavours</a> now!

Each link will now not load the page if a user clicks it “normally”, but will still work how you intended if the link is opened in a new tab or window, or bookmarked for later.

See the second example.

What about making the browser URL bar copyable?

Normally to change the page to somewhere different we write to window.location (eg. ). But the #hash part of the URL is updatable by the client without reloading the page. So we can set it to #tabA and #tabB as the user changes what they’re looking at. Then if they copied it to the clipboard all we’d need to do is look at the hash attribute of the window.location object in a very similar way to how we looked at the search attribute above:

dojo.addOnLoad(function() {
    // set the tab based on any URL at load-time
    if (window.location.hash) {
        var hash = window.location.hash.slice(1);   // strip leading "#"
        var newTab = dijit.byId(hash) || dijit.byId("myTabs").getChildren(0); // default to 1st tab
        dijit.byId("myTabs").selectChild(newTab);
    }
 
    // update the URL hash each time the tab changes
    dojo.connect(dijit.byId("myTabs"), "selectChild", function(tab) {
        // update the URL hash to the current tab
        window.location.hash = "#" + dijit.byId("myTabs").selectedChildWidget.id;
    });
});

See the third example

Note that when the URL is changed via window.location.hash, an entry is added into the browser history. Unfortunately in this example we’re not checking for the user hitting “back” or “forward”, so it won’t select the right tab. For more information on handling the back button gracefully, see dojo.back.

dojo._Url

window.location only applies to the URL of the current page. But hidden inside Dojo base is a URL handler that can do very similar stuff for any URL. Its not in the public API, but it hasn’t changed in a long while, and maybe it’ll become public soon. Basically you can instantiate it via new dojo._Url("https://example.com/foo/bar/?arg=123") and it builds an _Url object you can explore to get the hostname, scheme, query string, path, etc.

var u = new dojo._Url("https://example.com/foo/bar/?arg=123");

Each of the attributes of the URL can be edited, and you can call toString() to get a string URL back again. We can also use it to combine urls:

var u = new dojo._Url("https://example.com/foo/bar/", "../baz/101/");
u.toString() == "https://example.com/foo/baz/101/"
var u = new dojo._Url("https://example.com/foo/bar/", "/fizz/202/pop.jpg");
u.toString() == "https://example.com/fizz/202/pop.jpg"

In addition, it’s used in the super-handy function dojo.moduleUrl():

// assume dojo is at /js/dojo/
var u = dojo.moduleUrl("my.catz", "images/lol/00fluffy.jpg");
u.toString() == "/js/my/catz/images/lol/00fluffy.jpg"

With this we can find the URL to any resource that’s in our Dojo package system (including cross-domain) - which is great for creating images or loading templates. In fact, Dijit’s templatePath parameter uses dojo.moduleUrl() to define how to find widget templates… see any widget declaration for an example.

Conclusions & Next Steps

We’ve figured out that URLs aren’t only for the server-side, we can explore them just as well client-side too. We explored the window.location object which manipulates the current page URL, and learned some techniques for using GET parameters and #hash components for loading and navigating DHTML sites.

Then we moved onto dojo._Url for exploring any URL, and dojo.moduleUrl() for getting references to resources in our Javascript tree.

Earlier this month Kevin Dangoor from SitePen introduced Reinhardt: a Client-side Web Framework which includes URL dispatch and handling, utilising some of the ideas discussed above. Might be worth checking out.

As always, I’d appreciate your comments and feedback on the above!

Tags: