Cross-Browser Development Tips: Part 2 - JavaScript

JavaScript is hard. If you’re used to working on back-end development, it may be a huge context switch to get into the mindset of making asynchronous HTTP requests, defining callbacks, parsing JSON, and syncing the user interface (UI) with your application state. However, when done properly JavaScript is what turns websites into web applications, and overhauling our JavaScript was the biggest aspect of our recent front-end redesign. We also didn’t want to make the site unusable for our existing users. In my last blog post, Part 1 – CSS, I discussed Tinfoil’s decision to support IE8, and the lessons we learned along the way about how to write CSS that provides a consistent look across all major browsers, old and new. This post is all about JavaScript, and how we overcame the challenges in making Tinfoil work consistently in all of our supported browsers.

Stick to libraries that explicitly state support for IE8

This tip may be sound like a no-brainer, but when you find a really slick JS library that does exactly what you need, don’t forget to check if it works in all browsers you support. Any respectable library will explicitly declare which browsers they support. According to http://jquery.com/browser-support/, jQuery 1.x works with IE6 and above, while jQuery 2.x only works with IE9 and above, so we went with jQuery 1.x. While jQuery is great for DOM manipulation, we also needed a JavaScript library to deal with our complex data model and keeping the DOM in sync with model changes. For this, we turned to KnockoutJS, a Model-View-ViewModel library that we now cannot live without. Right on their homepage, they tout support for IE6 and above, which doesn’t surprise me for a library written by a Microsoft employee. Once you’ve picked your compatible libraries, try to perform as much of your logic using these libraries and do not rely on newer JavaScript functions.

Bad:

var site_ids = sites.map( function(s) { return s.id; } )

Good (jQuery):

$.map(sites, function(s) { return s.id; })

or (KnockoutJS):

ko.arrayMap(sites, function(s) { return s.id; })

Throttle, defer, and split expensive computations

On a whim the other day, I ran the a JavaScript benchmarking suite, SunSpider, on IE8 and Chrome on my Windows XP test machine. Chrome’s run took about 150ms, while IE8 took a staggering 3261.7ms, 21 times slower. This means that if you exclusively do your testing in a modern browser, you may not be aware of computations that could be inefficient. Even worse is the fact that the rendering thread and the javascript thread are one and the same, so slow javascript functions will lock up the browser and make it behave like it is frozen.

In general, but especially when writing your website to support IE8, it is best to throttle expensive javascript computations to when the user is likely not interacting with the page (scrolling, typing, clicking). If a computation is meant to run on every keystroke, perhaps to check the format of an email address, wait until the user has stopped typing for a reasonable amount of time, like 500ms and then run your computation.

If you need to perform a synchronous, expensive operation, it might be worth splitting it up into smaller chunks and wrap each chunk in a window.setTimeout. That way, as each chunk is completed, the browser will have time to redraw the screen and respond to queued up user interactions before performing the next chunk.

Detect features, not browsers

Sometimes you’ll want to use a cool JavaScript function (e.g. Array.map) or a useful built-in object (e.g. console), only to find out that IE8 does not support it. One thing you could do is parse the user-agent string to determine if the browser is IE8. If it is, you can choose to skip your function (for example, if you’re simply logging to the console) or use a shim.

if (navigator.userAgent.indexOf("MSIE 8") > -1) {
  // do IE8 stuff
}
else {
  // do non IE8 stuff
}

There are several problems with this. User agent strings are not stable. They are entirely made up by the browser making the request, and users can even override this string with whatever they want. A much better way to run newer JavaScript features is to detect the existence of the feature itself, rather than the browser. In the case of console.log, you could do

if (typeof console === "undefined" || typeof console.log === "undefined") {
  // do nothing, or use an alternative like 'alert()'
}
else {
  // use console.log without worry
}

In most cases, you can use this feature detection technique to define functions if they don’t exist. The Mozilla Developer Network is a great site for browsing functions, determining what browsers support what features, and most importantly, getting standards-compliant implementations of newer functions that may not be supported in IE 8. In the case of Array.map, you can take their code as is and drop it in the beginning of your javascript, instantly gaining that functionality in any browser. All code you’ll find there is either attached to the MIT License or is public domain.

In conclusion...

Making the conscious decision to support IE8 and beyond in our redesign was an enormous, but rewarding challenge. Now we have an interactive interface that relies much less on page refreshes and much more on quick AJAX calls and client-side state. On the CSS side, we were also able to delete over 100 images of things like buttons, arrows, pre-made gradients, and fake shadows because they were all re-written with CSS3. Obviously, you can test all we've done by signing up to see all the awesomeness we've built.

We learned a lot over the last few months, and I hope after reading this you have, too. We're always happy to help the community in developing great sites, so if you ever need help, just jump into our support chat. Happy coding!


Angel Irizarry

Angel Irizarry is the Software Samurai of Tinfoil Security, and a self-proclaimed software purist. All he needs to do his best work is a plain Linux machine with Git and Emacs installed. He loves everything about front-end development, like making pages interactive and super fast, even if that means digging in and optimizing some SQL. When he's not writing code, which isn't very often, you'll find him on his iPad scouring his RSS feeds for news and rumors of cool new gadgets.

Tinfoil Security Blog

Tinfoil Security provides the simplest security solution. With Tinfoil Security, your site is routinely monitored and checked for vulnerabilities using a scanner that's constantly updated. Using the same techniques as malicious hackers, we systematically test all the access points, instantly notifying you when there's a threat and giving you step-by-step instructions, tailored to your software stack, to eliminate it. You have a lot to manage; let us manage your website's security.