Neat Vanilla JS on HackerNews

I recently saw this video by Gordon Zhu highlighting the small but extraodinarily clever JavaScript on the Hacker News website.

I felt it would help me if I simplified it and broke down the steps for future reference. I'm using Paul Buchheit's code verbatim, I hope he doesn't mind, full credit to him.

What does this achieve?

This code is called when an anchor (a link) is clicked to vote up an item on Hacker News. It casts the vote and hides the vote up link so it can't be clicked again.

HTML

The markup for this is REALLY simple. It's a perfect example of progressive enhancement. The full URL is present in the HREF so if JavaScript is turned off the onclick is never called but the vote is still cast.

<a id="up_11659026" href="vote?for=11659026&dir=up" onclick="return vote(this);">Vote Up</a>  

JavaScript

No JQuery here! This is concise and elegant vanilla JS. The only JS on the site is following two functions, with the latter calling the former.

function hide(id){  
  var el = document.getElementById(id);
  if (el) { el.style.visibility = 'hidden'; }
}
function vote(node){  
  var v = node.id.split(/_/);
  var item = v[1];
  hide('up_'   + item);
  hide('down_' + item);
  var ping = new Image();
  ping.src = node.href;
  return false;
}

How does it work

  1. When the link is clicked the vote() function is called with the anchor (<a>) element passed as this
  2. The id, up_11659026, is split at the underscore and the parts passed to an array, v
  3. A new var, item, is given the value at v[1], in this case 11659026
  4. Two calls are made to the hide() function, one with the value up_11659026, and one with the value down_11659026
  5. The hide() function looks for an element with the passed id, if it finds it, the visibility of the element is set to hidden
  6. A var is created, ping, and is an instance of JavaScript's Image object.
  7. By setting the src of ping to the href of the clicked element, the URL is called (in this case a vote is cast by the back-end)
  8. Nothing is returned or rendered as it's unnecessary.
  9. We return false to stop the URL being followed by the browser
  10. If JavaScript is turned off in the browser, the URL is followed when clicked and the same outcome occurs.

Key only SSH

To cut back on the hacking attempts and make things just that little bit more secure, it's a good idea to disable the use of passwords to login via SSH.

Of course you'll need a way to access it so make sure you're public key is in your ~/.ssh/authorized_keys file.

To disable the use of passwords with SSH edit the sshd_config config file using something like nano. You'll need to run this as sudo.

sudo nano /etc/ssh/ssh_config  

Find the following lines and change them, or add them if they're missing:

RSAAuthentication yes  
PubkeyAuthentication yes  
ChallengeResponseAuthentication no  
PasswordAuthentication no  
UsePAM no  

One caveat here. I found on Ubuntu 12.04 that when I turn off UsePAM the banner I usually see when connecting with SSH is not shown.

To fix this I uncommented and ammended the line which reads #Banner /etc/issue.net:

Banner /etc/motd  

Of course you'll need to restart sshd, depending on what service management system you use, enter the following:

sudo service ssh restart  
or  
sudo /etc/init.d/sshd restart  

Important Don't lose your private keys which match the public keys you've used, or you'll never get back in!

Flexbox space-between and the last row

I'm smitten with flexbox. To really get to grips with it I built a comprehensive, minimum styling site to really grasp how to do layout with it.

I created a 'card' layout with eight cards and had two rows of four. Looked great:

Two rows of four cards

So I tested the responsiveness and at my desired breakpoint the rows change to three cards (thanks to flex-wrap) and I was struck something that didn't look good:

Eight cards across three rows

Despite a lot of playing around with flexbox properties and margins etc. I could not find a pure css solution so I had to embrace some 'progressive enhancement' and turn to JavaScript.

It struck me that in order to shift the last element along I would need an invisible element. Since I couldn't be certain of how many cards there would be, I'd need to add enough invisible elements to bring the total up to a number divisible by both three and four. The smallest number that divides by both is twelve.

The first function called takes an ID of the element that contains the cards. It then counts the child elements and passes that to the checkCount() function:

var container = document.getElementById(container_id);  
var card_count = container.children.length;  
checkCount(card_count, ...  

The checkCount() function takes the total number of cards and a callback function as arguments. By using the modulus operator (%) we can test to see if any remainder exists after dividing by twelve. Since a remainder other than zero is treated as true we know when we hit the right number when the if statement is false.

So if we do get a remainder, we increment the count and pass that to the same function. Hurrah for recursion!

function checkCount(current_total, next){  
  if(current_total % 12){ // 12 is smallest number that 3 and 4 go in to
    current_total += 1; // Proper way to do ++
    checkCount(current_total, next); // Recursion!
  } else {
    next(current_total);
  }
}

With the final count returned we can find the number of extra elements required by subtracting the original card count from it. Using this in a for loop we create elements with the class dummy_card and append them to the original container.

var dummy_element;  
for(i=0; i<(final_count-card_count); i++){  
  dummy_element = document.createElement('div');
  dummy_element.className = 'dummy_card';
  container.appendChild(dummy_element);
}

By giving the dummy_card a height of zero in the CSS it won't affect the look of the page. Although this approach does pollute the DOM a bit, by using empty div elements we don't affect the semantics. Seems to work.

Eight cards across three rows, fixed

Here's the full code:

function checkCount(current_total, next){  
  if(current_total % 12){ // 12 is smallest number that 3 and 4 go in to
    current_total += 1; // Proper way to do ++
    checkCount(current_total, next); // Recursion!
  } else {
    next(current_total);
  }
}
function addDummyElementsToCards(container_id){  
  var container = document.getElementById(container_id);
  var card_count = container.children.length;
  checkCount(card_count, function(final_count){
    var dummy_element;
    for(i=0; i<(final_count-card_count); i++){
      dummy_element = document.createElement('div');
      dummy_element.className = 'dummy_card';
      container.appendChild(dummy_element);
    }
  });
}
addDummyElementsToCards('cards');  

Internet Explorer EOL

I no longer support any version of Internet Explorer prior to version 11. Microsoft has, as of today, ended their support for them. This means that no bug-fixes, compatibility fixes, and more important security updates will be forthcoming.

There is no longer any compelling reason to use one of these old browsers. Security alone should justify moving to IE11, Edge, or one of the other free browsers.

I pulled up the analytics of the sites I have access to. And after a quick bit of tabulation and some maths I can say that on average 3% of visitors to the sorts of sites I build use IE with a version number less than 11.

Who knows what proportion of that 3% actually convert to completing some goal. Frankly, it's not worth the effort supporting them.

Diffie-Hellman for TLS

After successfully enabling Let's Encrypt for my domain, I ran the test over at SSL Labs and was disappointed to see I only scored a C grade.

It seems that because my Nginx configuration supported SSLv3 it was susceptible to the POODLE attack. And thus regardless of anything else the grade was capped at C. This was easily fixed however by adding the following line to nginx.conf:

http {  
    ...
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ...
}

I ran the test again but this time only scored a B grade. Here's why:

Warning! This site uses a commonly-shared 1024-bit Diffie-Hellman group, and might be in range of being broken by a nation-state. It might be a good idea to generate a unique, 2048-bit group for the site.

Luckily, the kind folks over at weakdh.org provide a Guide to Deploying Diffie-Hellman for TLS. To summarise, do the following:

First thing to do is generate a dhparams.pem. I put it in my /etc/nginx/sites-available directory, but it doesn't really matter where you put it as long as it's a fairly safe location:

openssl dhparam -out /etc/nginx/sites-available/dhparams.pem 2048  

Now in the server block for your domain (by default this is in /etc/nginx/sites-available/default) add the following lines:

server {  
    ...
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/sites-available/dhparams.pem;
    ...

Now reload Nginx:

sudo nginx -s reload  

These simple steps have garnered me an A grade.

A grade on SSL Labs