Most web pages and applications deal with URLs in some way. This could be an action like crafting a link with certain query parameters or URL-based routing in a single-page application (SPA).
A URL is just a string that complies with some syntax rules as defined in RFC 3986, “Uniform Resource Identifier (URI): Generic Syntax”. There are several component parts of a URL that you may need to parse or manipulate. Doing so with techniques like regular expressions or string concatenation isn’t always reliable.
Today, browsers support the URL API. This API provides a URL
constructor that can create, derive, and manipulate URLs. This API was somewhat limited at first, but later updates added utilities like the URLSearchParams
interface that simplifies building and reading query strings.
Browser Location and Navigation
The location property of the Window object refers to a Location object, which represents the current URL of the document displayed in the window and also defines methods for making the window load a new document.
A window location is an object that contains a lot of information about the URL
Overview
Parsing URLs
// URL : http://example.com/page?name=John&age=25&city=New%20York
// location.search = ?name=John&age=25&city=New%20York
function urlArgs(url) {
url = new URL(url);
const result = [];
url.searchParams.forEach((value,key)=>result.push({key,value}));
return result;
}
[{key: 'name', value: 'John'}
{key: 'age', value: '25'}
{key: 'city', value: 'New York'}]
Methods of window.location
window.location.assign(url): Loads a new document at the specified URL
window.location.replace(url): Replaces the current document with a new one at the specified URL without adding a try to the browser's history
window.location.reload(forceReload): Reloads the current document.If the forceReload parameter is set to true
Navigate to a new page
//Using window.location.assign
window.location.assign('google.com');
//Using window.location.href
window.location.href="google.com";
//Prevent users from clicking the back button to return to previous page
window.location.replace('google.com');
New URL API
Resolving a Relative URL
function resolveUrl(relativePath, baseUrl) {
return new URL(relativePath, baseUrl).href;
}
// https://example.com/api/users
console.log(resolveUrl('/api/users', 'https://example.com'))
// https://example.com/api/v1/users
console.log(resolveUrl('/api/v1/users', 'https://example.com'));
// https://example.com/api/v1/users
console.log(resolveUrl('/api/v1/users', 'https://example.com/api/v2'));
// https://example.com/api/v1/users
console.log(resolveUrl('../v1/users/', 'https://example.com/api/v2'));
// https://example.com/api/v1/users
console.log(resolveUrl('users', 'https://example.com/api/v1/groups'));
Better reading and writing URL
const url = new URL("https:google.com");
url.searchParams.set('model',"model");
url.searchParams.set('locale',"locale");
url.searchParams.set('text',"text");
url.toString();
//Instead of
const url =
`https:google.com?model=${model}&locale=${locale}&text=${text}`
// Bad -> this is too verbose and the value is not even encode it
Encoding reserved characters in a query parameters
const url = new URL('https://example.com/api/search');
//Contrived example string demonstrating several reserved characters
url.searchParams.append('q', 'admin&user?luke');
//Result
https://example.com/api/search?q=admin%26user%3Fluke
%26
in place of &
, and %3F
in place of ?
. These characters have special meaning in a URL. ?
indicates the beginning of the query string and &
is a separator between parameters.Reading query parameters
function getQueryParameters(inputUrl) {
// Can't use an object here because there may be multiple
// parameters with the same key, and we want to return all parameers.
const result = [];
const url = new URL(inputUrl);
// Add each key/value pair to the result array
url.searchParams.forEach((value, key) => {
result.push({ key, value });
});
// Results are ready!
return result;
}
Creating a simple client-side router
How does react-router work?
History.pushState and popState events
The global history object's pushState method changes the current URL without reloading the page. It adds the new URL to browser's history
First, an object containing arbitrary data to associate with the new history entry. This state data is available from the
popstate
event as well.The second argument is unused, but must be given. You can use an empty string here.
Finally, the new URL. This can be an absolute URL, or a relative path. If you use an absolute URL, it must be on the same origin as the current page or the browser throws an exception.
// Route definitions. Each route has a path and some content to render.
const routes = [
{ path: '/', content: '<h1>Home</h1>' },
{ path: '/about', content: '<h1>About</h1>' }
];
function navigate(path, pushState = true) {
// Find the matching route and render its content
const route = this.routes.find(route => route.path === path);
// Be careful using innerHTML in a real app!
document.querySelector('#main').innerHTML = route.content;
if (pushState) {
// Change the URL to match the new route
history.pushState({}, '', path);
}
}
With this navigate function, we can override the behavior of link
<a href="/">Home</a>
<a href="/about">About</a>
document.querySelectorAll('a').forEach(link => {
link.addEventListener('click', event => {
// Prevent the browser from trying to load the new URL from the server!
event.preventDefault();
navigate(link.getAttribute('href'));
});
});
To make this a full solution, there is one more necessary piece. If you click one of these client-side routes, then click the browser’s Back button, nothing happens. This is because the page isn’t actually navigating but just popping the previous state from the router. To handle this scenario, you also need to listen for the browser'spopstate
event and render the correct content.
window.addEventListener('popstate', () => {
navigate(window.location.pathname, false);
});
When the user clicks the Back button, the browser fires the popstate
event. This changes the page URL back, and you just need to look up the content for the route matching the current URL. In this case, you don’t want to call pushState
because that adds a new historical state. This probably isn’t what you want since you just popped an old history state off the stack.