Do websites need to look the same in every browser?

There are numerous awesome new features introduced in HTML5 and CSS 3, but neither specification is finalized and browser support for them is still pretty unreliable. After getting a taste of what these new technologies can do, it can be hard to resist using them and to wait for better browser support. My position for a long time has been that, although it's frustrating as a developer, it's best to create things for the lowest common denominator and not use features that don't have reliable support across browsers. But my thoughts on the subject have been changing recently.

One of the catalysts for this change was a spectacular quote from Douglas Crockford that I came across:

If a web browser is defective, causing errors in the display or performance of the page, should the page developer struggle to hide the browser's defects, or should the defects be revealed in hope of creating market pressure to force the browser maker to make good? By which approach is humanity better served?

The debate as to whether or not a developer should support bad, buggy browsers (henceforth referred to as IE) is not a new one. Most often it is simply not a choice for the developer. Most businesses will require support for IE due to its sheer market share and the monetary consequences of not doing so. Even on a project without requirements dictated by an employer, choosing not to support IE puts you at a disadvantage, because chances are there are other developers out there making similar products that do support IE. And when faced with a choice, which are clients going to choose?

Because of these reasons, I've always erred on the side of caution and supported IE. But reading that insightful quote from Douglas Crockford reinvigorated the debate in my mind. I started to rethink how important supporting IE really is and how to find a better compromise in regard to feature support in browsers. There's an emerging school of thought that we should rid ourselves of the preconception that a website should look exactly the same in every browser. I've seen this new attitude come up in several different places recently.

So, does a website need to look the same in every browser? The short answer: no.

My new attitude is to think of browser support with a progressive enhancement approach. I can dramatically improve the look of a site with new CSS 3 effects such as box-shadow. But IE doesn't support it. Should I forego its use? Or use some JavaScript to recreate the effect? Neither! I should use it. Users with browsers that support it will see the fancy version, and IE users will see something more basic, but still fully functional. Since they have not seen it any other way, they're not disappointed, and nothing looks wrong or broken. They're none the wiser.

I feel that this approach is a good compromise on what Douglas Crockford's quote suggests. Instead of crying, "IE be damned!" and allowing it to break, it inverts the sentiment to use a positive context. Instead of punishing IE users for using an outdated browser, it continues to present the baseline for them, and rewards users of modern browsers with extra sparkle. This encourages the use of modern browsers just like the "let IE break" attitude, but keeps everything perfectly useable and presentable to the least common denominator.

Understanding jQuery 1.4's $.proxy() method

No sooner than I wrote about how to control a JavaScript function's context with the Prototype Function.prototype.bind, jQuery 1.4 is released and one of its new tricks is jQuery.proxy, which we can use for exactly the same purpose.

To refresh you, the problem that needed solving was how to preserve a reference to the calling object when the value of this is changed. Consider the following:

MyModule = function() {
  this.$div = $('#testdiv');
  this.myString = "Hi! I'm an object property!";

  this.$div.click(this.handleClick);
};

MyModule.prototype.handleClick = function() {
  console.log(this.myString); // undefined
};

var m = new MyModule();

When you click on the div, we are given undefined, because this now refers to the DOM element that triggered the event and not the instance of MyModule we've stored in the variable m. The element does not have a myString property, and hence, it is undefined. So how do we access the myString we want? The solution I wrote about previously was to use Function.prototype.bind from the Prototype library, which allows us to control what this will refer to inside the function we're calling. But now in version 1.4 of jQuery, we can handle this situation with the new jQuery.proxy method. The method has two signatures:

jQuery.proxy( function, scope )
jQuery.proxy( scope, name )

In the first form, the function argument is the function we're calling, and the scope argument sets the context the function should be called in. In the second form, the scope comes first, and the name argument is a string that gives the name of the function we're calling. Note that the function provided in name should be a property of whatever object we're using as the scope. Let's look at an example to make this more clear.

MyModule = function() {
  this.$div = $('#testdiv');
  this.myString = "Hi! I'm an object property!";

  this.$div.click($.proxy(this.handleClick, this));
};

MyModule.prototype.handleClick = function() {
  console.log(this.myString); // Hi! I'm an object property!
};

var m = new MyModule();

Now when we click on the div, we see the result we want, because this is now bound to our m object, which is an instance of MyModule. This uses jQuery.proxy's first signature. We could achieve exactly the same effect using the second signature by using this line instead:

this.$div.click($.proxy(this, "handleClick"));

The small "gotcha" that remains is how to access the DOM element that triggered the event, now that we've changed the value of this within our function. The good news is that, like before, all event handling functions are passed an event object with details about the event that occurred. Within that event object is a property called currentTarget, which holds exactly what we're looking for: the DOM element that triggered the event. Here is an example of how to access it:

MyModule.prototype.handleClick = function(event) {
  console.log(this.myString); // Hi! I'm an object property!
  console.log(event.currentTarget); // <div id="testdiv">
};

Thanks to jQuery.proxy, we now have the ability to control function scope with jQuery alone. To read more about all the new features in jQuery 1.4, check out John Resig's overview on The 14 Days of jQuery.

Organizing JavaScript with Namespaces and Function Prototypes

Keeping your JavaScript code organized and readable can be a bit of a task. Since I started with jQuery, most of the JavaScript for my applications has just been inside a giant $(function() { }) block, and as the code grows longer and more complex, it becomes much harder to find the thing you're looking for and edit it later. I was searching for a design pattern that would help me organize my code in a way such that I wouldn't dread looking at my JavaScript files in the future. My solution came in the form of namespacing and building modules with function prototypes.

Namespaces

A namespace is a context in which variables can exist without conflicting with other variables of the same name elsewhere. For example, say I write a function called insert for inserting some text into a page. Then I include a 3rd party library that also defines a function called insert (and for the sake of argument, is not namespaced). My original insert function has now been overridden by the library's version. This type of collision is rarely what you want and can introduce some major headaches. You can get around this by encapsulating your code in an object that is unlikely to collide with other libraries and then accessing it with MyObject.function. It's a good idea to put all the code for your project under one main object (named after your project) and then separate the different sections of your code into modules within that object. Start with this at the very beginning of all your JavaScript code (I'll be using my upcoming More Things Need To project as an example):

// file: main.js
if (typeof MTNT == 'undefined') {
  MTNT = {};
}

This creates a new empty object called MTNT, but does not overwrite it if for some reason it already exists. Now, you can write your code in modules (that are themselves further namespaces) that exist as properties of the global namespace object. It is a good idea to also put these modules in separate files to keep things clean. For example:

// file: mtnt.form-validator.js
MTNT.FormValidator = function() {
  // code for form validation goes here
}

Now, instead of adding more spaghetti code to your main.js JavaScript file, you can simply instantiate your module: new MTNT.FormValidator().

Function prototypes

This brings us to the second part of organizing your code, which is to split up different behaviors into prototype functions within each module. JavaScript does not use classical inheritance patterns in its object orientation – it uses a technique called prototyping. While the explanation of this is really beyond the scope of this article, in a nutshell, the prototype property of a function myFunction exists only once in memory and is shared by every instance of myFunction. This will become more clear in an example (I'm using jQuery here):

// file: mtnt.form-validator.js
MTNT.FormValidator = function(myProperty, form) {
  // initialize some variables
  this.myProperty = myProperty;
  this.form = form;
  // ...

  // call a function to valid the form data
  this.form.submit(this.validate); // "submit" is jQuery's submit
}

MTNT.FormValidator.prototype.validate = function() {
  // validate the form data
  // ...
}

Using this technique, all instances of MTNT.FormValidator will have a validate method. This allows us to separate the methods within our module like this without nesting functions inside functions. It also saves memory because each method only exists in the function prototype and a new copy of the method is not created for each instance of MTNT.FormValidator.

A very serious "gotcha"

This introduces a new complication, however. By separating our methods in this way, we change the context of each. Consider the value of this inside MTNT.FormValidator. It refers to the current instance of that module. But inside MTNT.FormValidator.validate, the value of this has been changed by jQuery's submit method to refer to the DOM element that triggered the event. This means that we won't be able to access myProperty from MTNT.FormValidator because this.myProperty will refer to a non-existent property on a DOM element from within the validate method. How do we solve this?

Function.prototype.bind

The solution is a function we will extract from the Prototype JavaScript library: bind. Let's take a look at it and then I will explain what it does. Include this at the top of your main JavaScript file, along with the code that initializes your main namespace:

if (typeof Function.prototype.bind == 'undefined') {
  Function.prototype.bind = function() {
    var __method = this, args = Array.prototype.slice.call(arguments), object = args.shift();
    return function() {
      var local_args = args.concat(Array.prototype.slice.call(arguments));
      if (this !== window) local_args.push(this);
      return __method.apply(object, local_args);
    }
  }
}

Without getting into too much detail, bind uses the magic of JavaScript's call and apply functions to create a function call forced into a particular context. The first parameter to bind is the context you wish to call the function in. Any additional parameters are passed on as parameters to the function you're calling. We can now replace this line:

this.form.submit(this.validate);

with this:

this.form.submit(this.validate.bind(this));

Now when validate is called, this will still refer to the instance of MTNT.FormValidator and we can access myProperty with this.myProperty.

Where did my parameters go?

While we're now able to access the original this from our object, the reference to the DOM element that jQuery normally provides is gone. Or is it? Are we simply trading one this for another? The answer is no. The original this is still there, but it's been moved into a parameter. Let's change the signature of our validate method to take this into account:

MTNT.FormValidator.prototype.validate = function(event, form) {
  // "event" is the event object that is normally passed into event handlers by jQuery
  // "form" is the original value of "this" that jQuery would have set

  // validate the form data
  // ...
}

Now we have the best of both worlds. We can control the context our methods are called in, and we still have access to everything that was originally provided to them.

Ugh, mixing Prototype into jQuery?

When it was first suggested to me to use Prototype's bind as a solution for this context problem, I was hesitant. It seemed like a pretty nasty hack, for a couple of reasons. One reason is the same reason many people criticize the Prototype library in the first place: that it overrides built-in JavaScripts objects like Function, which very strongly violates the namespacing idea discussed at the beginning of this article.

While this is a very valid concern, it is worth noting that this particular usage of Function.prototype.bind has become so common and is so well accepted that it is even part of the recently released ECMAScript 5 specification, meaning that bind will be built into future versions of JavaScript itself, and as such, the namespacing considerations of adding bind to JavaScript's Function prototype are nearly moot.

The other thing that bothered me about this solution was just the idea of needing something extra in order to make jQuery behave. I looked around for a "jQuery equivalent" way of doing this, and my conclusion is that there really is no direct equivalent. jQuery encourages use of the JavaScript module pattern instead of the prototype pattern I describe here. This is not so much an equivalent as it is an entirely different approach altogether. I've experimented with the module pattern a bit, but for the moment I feel using function prototypes fits my style better. The choice, of course, is entirely up to you.

Conclusion

What I present here is simply one way to organize your code. As with most decisions you make when writing JavaScript, there is no one right way, and that's part of what's so great about JavaScript: it's expressive and allows you to write things in a way that works best for you. That said, the ideas I present here are very common, solid solutions to making your JavaScript more modular and readable. This promotes better maintainability and code reuse, which is something all developers should strive for.

1 10 12 14 18