The nice trick with anchors and location
There’s a really nice way to get the pathname off URLs that aren’t the current window.location
. If you set them as the href
of an anchor element, the anchor element actually implements the same location
Protocol as window
.
// on https://adamdawkins.uk/articles
window.location.pathname // => “/articles”
var url = document.createElement('a');
url.href = "https://adamdawkins.uk/articles";
url.pathname; // => "/articles"
Where can this be useful? On any site where you need to manipulate or analyse URLs client-side, or, as in my case, when you’re using browser.pushState
but still want to read state from a location
object in the same way regardless.
On a project recently I used it like this:
// getState :: Location -> Object
const getState = (location) => ({
page: location.pathname,
...getQueryParams(location)
})
I’d call getState(window.location)
on page load, but, when I changed the URL with history.pushState
I needed to pass that URL into my getState
function as well. Instead of trying to change the simplicity of getState
(takes a location, returns an object, lovely) - we can use the fact that anchors implement the location protocol to get something we can pass to this.
// toLocation :: URL -> <a>
const toLocation = (url) => {
const anchor = document.createElement('a');
anchor.href = url;
return anchor
}
const newURL = setQueryParams({foo: 'bar'}) // returns the whole URL, e.g. "https://adamdawkins.uk/articles?foo=bar"
const newState = getState(toLocation(newURL))
Note the the anchor DOM Element itself implements the Location protocol, not anchor.location.
Internet Explorer and relative pathnames
Lovely stuff. Enter Internet Explorer, stage right.
Internet Explorer takes relative URLs and does not treat the initial /
as part of the pathname
Using our code above:
// All other browsers
toLocation("/articles?foo=bar").pathname // => "/articles"
// Internet Explorer
toLocation("/articles?foo=bar").pathname // => "articles"
Great, thanks.
Maybe I was just unlucky, but I was matching on that /
to check which page I was on:
if (state.page.match(/\/checkout/delivery\//)) {
// …
}
And it would work, unless I pushed state in Internet Explorer, now state.page
is “checkout/delivery”.
What to fix?
There’s a dilemma here. The easiest fix is to loosen my regex on the page, and not check for a preceding /
, but the problem is - that is what I mean - I do want to match /checkout/delivery
, and it feels wrong to change that because IE has anxiety issues about the initial /
.
The fix I went for was to create a simple pathname()
function that would replace the /
if it had been lopped off by IE:
// pathname :: Location -> String
const pathname = (location) => (
// in IE location.pathname strips the first `/` from relative URLs, here we add it back
location.pathname.match(/^\//) ? location.pathname : `/${location.pathname}`
)
Phew.
This article was originally published on adamdawkins.uk
Top comments (0)