Revisiting My 2010 JavaScript Library

A Blast From the Past
Fund this Blog

This blog has been running for 12 years, and it's only natural that I'd start thinking about a fresh, modern design to match the times. Before diving into what that new design should look like, I went to inspect the current code and understand its inner workings. That's when it hit me: the initial JavaScript library I wrote dates all the way back to 2010.

It's a true relic of the past, a testament to just how far JavaScript has come and all the hoops we used to jump through to get a half-decent website working. Join me on a trip down memory lane as I describe the problems I was trying to solve back then, the web development landscape of the time, and the "clever" solutions I cooked up.


The Wild West of Browser Wars

In the early 2010s, we were still deep in the throes of the browser wars. Implementing a feature in one browser often meant it behaved differently in another, or sometimes, didn't exist at all, forcing us to reimplement it from scratch. Internet Explorer (IE) was still the dominant browser, and the most popular versions didn't support HTML5 without significant workarounds. You could write HTML5 tags like <article>, <header>, <footer>, etc., but they'd often render as inline text with no CSS support in IE.

My custom library, which I cleverly named "Simply," started with this intriguing line of code to add HTML5 support to IE:

(function(d) {
    'abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.replace(/\b\w+/g,function (a){
        d.createElement(a);});
})(document);

This self-invoking function executes immediately. It takes a space-separated list of HTML5 tags, splits them by word, loops through each, and creates a DOM element. The reason? Even though IE didn't natively support these elements, creating them via JavaScript made them recognizable by the CSS engine. (To this day, I'm not entirely sure why, but it worked!) Armed with this script, my IE users could finally view those semantic HTML5 tags properly styled throughout the website.


Custom Utilities for Missing Features

Next, I added a utility function to the Array.prototype to easily loop through elements. At the time, built-in methods like Array.map or Array.forEach weren't widely available:

Array.prototype.loop = function (fn){
    var l = this.length;
    for (var i = 0; i < l; i++){
        var item = this[i];
        fn.apply(item, [i]);
    }
};

This allowed me to iterate over an array without writing a for loop every single time. For example:

[1, 2, 3].loop(function (index) {
    console.log(index, this);
});

Looking back, I'm not sure why I used this for the current array value; passing it as a parameter would have been more intuitive. I also added another property to the Array object that quickly became obsolete as browsers caught up:

Array.prototype.isArray = true;

This was my "clever" way to determine if an element was an Array:

var a = 1;
var b = [1];
if (a.isArray)
    console.log("a is array");

if (b.isArray)
    console.log("b is array")
// Output: b is array

However, Array.isArray eventually became a valid, native method, and I was inadvertently polluting the prototype. Live and learn!


The Mysteries of Browser Detection

Next up is one of my favorites. I can't find the original documentation for this, but I stumbled upon a truly "clever" trick to detect if the browser was IE:

var isIE = (!+"\v1") ? true : false; // true only in IE

This value was true only in IE, and nowhere else. Why? I still have no idea! But it allowed me to write JavaScript specifically targeting IE users:

var isIE = (!+"\v1") ? true : false;
if (isIE) {
    alert("Switch your browser to Chrome!");
}

This little hack worked like a charm right up until Internet Explorer 11.

Another persistent annoyance was event handlers. Most browsers used addEventListener, but IE, in its unique wisdom, opted for attachEvent. I certainly didn't want to check which method was supported every time I wanted to set an event. Instead, I could test it once and save the appropriate method to a variable for reuse:

var obj = document.createElement("a"),
addEvent = obj.addEventListener? 'addEventListener': 'attachEvent',
removeEvent = obj.removeEventListener? 'removeEventListener':'detachEvent',
on = obj.addEventListener? '':'on';

Tackling Asynchronous Script Loading and Dependencies

These utility functions were genuinely useful at the time. However, a major problem I was trying to solve was loading scripts asynchronously. The async attribute wasn't widely supported yet. If you loaded scripts asynchronously, you'd immediately run into the challenge of organizing dependencies. Imagine loading jQuery asynchronously: you'd then have to monitor when it was ready before loading other scripts that depended on it. But that would defeat the purpose and increase load time!

My solution was to load all scripts asynchronously and define a single object that managed dependencies in the background. Here's how I did it:

var _simply = _simply || [];

That's it! I defined this variable at the very top of the page, inline, and used the push command to define functions that would run when their dependencies were ready. This approach was inspired by the Google Analytics _gaq script.

Here's how it worked:

// 1. Define the variable if it doesn't exist
var _simply = _simply || [];

// Write your script that depends on other scripts as an array value
_simply.push(["onReady", function() {
    // Do things that depend on the Simply.js script.
}]);

All we did was add a function inside an array called _simply. When the Simply.js script eventually loaded, the first thing it did was check the values inside that array and run them. Then, it would redefine the _simply object with a new push() method that directly called the function:

// 2. Run all functions then redefine the _simply object.
const pushit = function(opts) {
    if(opts[0] === "onReady") {
        var fn = opts[1];
        fn.apply(window, []);
    }
};

// run all the dependent functions
_simply.loop(function() {
    pushit(this);
});

// redefine _simply
_simply = {
    push: function (opt) {
        pushit(opt)
    }
};

This allowed me to set all my scripts as asynchronous without worrying about dependencies between them.


More Handy Helpers from the Era

Here are a couple more functions that proved incredibly useful back then:

// Yes, selecting by class name was not natively supported in all browsers!
getElementsByClassName = function (className, context) {
    var doc = context || document;
    if (doc.getElementsByClassName){
        return doc.getElementsByClassName(className);
    }
    var elems = doc.getElementsByTagName("*"),
        i = 0,l = elems.length,
        filts = [];
    for (;i<l;i++){
        if (elems[i].className.indexOf(className) !== -1){
            filts.push(elems[i]);
        }
    }
    return filts;
};

And, of course, we also had to check if the client was on a mobile device:

isMobile = (function (){
    return (function (a) {
        return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)
                || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))
                );
    })(navigator.userAgent || navigator.vendor || window.opera);
})();

I also had a custom jQuery loader that allowed me to write jQuery code that would first load the library in the background and only trigger when it was ready.


Looking Back, Looking Forward

Over the years, I've naturally removed many of these utilities as they became redundant. In fact, even jQuery is rarely needed anymore since most of the features I use are now natively supported by all modern browsers.

But these old scripts offer a fascinating glimpse into the past. How we used to write code in a much more unstable and fragmented ecosystem, and how much we had to cater to the quirks of Internet Explorer. I certainly don't miss those days, but it's incredibly fun to revisit them just to see how far we've come. I can only imagine what the web development landscape will look like 15 years from now. How will I look at my "new" code then and judge it? That's something I'm eager to find out.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only