QR Code contains TinyURL of this article.JavaScript: Does Node Exist in the DOM?

hands typing on MacBook, computer code on screen

When I added the Turbolinks™ software to this website I had to make a couple of minor changes to one or two of my JavaScript methods to accommodate it.

One of the fundamentals of Turbolinks is that the window.onload event fires only on the first visit and with full reloads. Transitioning from one page to the next (within the same website), does not fire the event.

Amongst other things, I bind an event each to the Previous Post and Next Post buttons. These events handle the keyboard shortcuts for those buttons, H and L respectively.1  Before Turbolinks, I used the window.onload event to initialise their listeners

The problem for me was, because window.onload fires only once, if the visitor is on the most recent weblog entry when it fires (ie. there’s no Next Post button) then the L binding is not set. Thus, when the visitor navigates to another weblog entry, where there might be a Next Post button, the keyboard shortcut for it is not established. The same happens with the H binding if the visitor’s ingress point is at the first weblog entry (ie. no Previous Post button).

Fortunately, Turbolinks triggers a turbolinks:load event when it has transitioned to a new page. So one needs only set a listener on that event, in order to replicate the window.onload behaviour.

document.addEventListener('turbolinks:load', function() {
  // …
})

This is all well and good, but it presented me with a new dilemma. The code I used to bind the keyboard listeners looked like this:2

function keyboardShortcuts() {
  Mousetrap.reset();
  Mousetrap.bind('h', function() { document.getElementById('prev').click(); }, 'keyup');
  Mousetrap.bind('l', function() { document.getElementById('next').click(); }, 'keyup');
}

The function above works just fine, when both the Previous Post and Next Post buttons are on the page. But, if either is missing (as they are on the first and last weblog entries) then the JavaScript fails because it tries to bind an event listener to a non-existent DOM node.

And… if JavaScript fails at any point, the remaining code does not execute. Hell’s bells.

Therefore, I need to determine which of the pagination buttons is available each time the turbolinks:load event fires, so that I can bind the keyboard listeners accordingly.

I found a solution on the Mozilla Developer Network. It looks like this:

function isInPage(node) {
  return (node === document.body) ? false : document.body.contains(node);
}

With that method embedded in the Perpetual βeta’s JavaScript file I was able to revise my keyboardShortcuts function as follows:

function keyboardShortcuts() {
  Mousetrap.reset();
  if ( isInPage(document.getElementById('prev')) ) { Mousetrap.bind('h', function() { document.getElementById('prev').click(); }, 'keyup'); }
  if ( isInPage(document.getElementById('next')) ) { Mousetrap.bind('l', function() { document.getElementById('next').click(); }, 'keyup'); }
}

Works a treat.

  1. Because vi. 😃 ↩︎

  2. I use the Mousetrap JavaScript library by Craig Campbell ↩︎