The real world case for unobtrusive JavaScript
Unobtrusive JavaScript (UJS) has gained mainstream acceptance. Except for a few holdouts, the collective conscience of web developers has changed from thinking inline JavaScript is good enough to accepting that UJS is the modern and right way to add behaviour to web pages. But this is not the same thing as really understanding how JavaScript relates to modern web development; I believe there’s a word for this kind of collective behaviour.
I want to explain the real world, pragmatic and practical reasons for unobtrusive JavaScript. Actually, I think the word unobtrusive is misleading because unobtrusiveness is just a side effect of what I will call structured JavaScript. It is not purity for purity’s sake that should drive you towards a cleaner, more structured, more reusable and more scalable way to program JavaScript, but the benefits inherent in these qualities, including keeping your sanity when dealing with less-than-trivial functionality.
First, let’s examine the weaknesses of unstructured or obtrusive JavaScript:
- It’s unstructured: This should be enough of a weakness in itself. Lack of structure results in messy code that increases the overhead of making changes to a point where it becomes nearly impossible.
- It doesn’t scale: Any sufficiently complex web application will implode from its own weight if you keep adding inline JavaScript. What seems simple to begin with will eventually evolve into a mess of inline event handlers, global functions and variables clashing with each other and strong coupling. It will become increasingly difficult to find anything at all and adding or changing functionality will eventually become a dreaded, near-impossible task where even the smallest change could bring the whole thing down.
- It’s not testable: It’s much, much harder to write automated tests for messy, coupled code than it is for properly structured code.
- It’s not reusable: Yes, you may have some server-side function that generates the same code over and over again that you could call reusable, but that’s like saying Java is conceptually simple because you have this huge IDE generating your huge classes, interfaces and XML configuration files for you. You’re still littering your HTML with the exact same code over and over, increasing page load and complexity along the way. You can’t abstract away JavaScript.
- It adds more coupling to your code: Inline code couples your JavaScript to something as volatile as HTML, resulting in easier breakage of semi-unrelated components of your application. You might argue that having it all in the same place makes it easier to see the coupling and change both at the same time, but well-structured unobtrusive JavaScript has considerably less coupling to begin with and can handle much more change to the HTML tree without breaking than inline code can ever dream of.
- It forces responsibility overlap: By inlining JavaScript you force both designers and programmers (both server- and client-side) to be responsible for and understand the details of each other’s work. This is not entirely avoidable; the content, style and structure of your page will overlap, but by staying unobtrusive you can keep this to a much more sane level. A designer shouldn’t have to journey deep inside the intestines of your inline JavaScript to change the way an element looks.
- Your page elements can’t have their own state: Unobtrusive JavaScript often ignores this as well, but when using inline code you’re making sure your page elements (like a post, comment, etc) can’t have their own internal state; they’re not self-conscious. This is a basic requirement for good object-oriented programming which can make it much easier to deal with complexity.
The basic tenets of good programming apply to JavaScript just as much as they do to Ruby or Java programming. Modularity, proper structure, reusability and encapsulation provide exactly the same benefits for JavaScript programming as they do for other languages and “obtrusive” JavaScript breaks all of these practises. Inline JavaScript is to structured JavaScript what PHP is to Ruby on Rails: Its simplicity only works in its favour for very simple applications. JavaScript is a rebellious teenager in need of structure and discipline; you’re the parent who must send it to boarding school.
So what are these benefits I talk about? They’re basically the opposite of the weaknesses of not following best practises:
- It’s structured: Again, this is a strength in itself. Structured code is a pre-requisite for most of the other best practises.
- It scales: Once your code is properly modularised, decoupled and encapsulated, it becomes so much easier to add or change functionality. It’s considerably less work than it would have been with an inline Frankensteinian mess of an application.
- It’s less complex: Complex code is more difficult to understand, change and test.
- It’s testable: You can test discrete components of your application’s functionality without setting up scaffolding to support it or having unrelated tests break because your code is coupled.
- It’s decoupled: You can change one part of the page’s functionality without worrying that something completely unrelated will break.
- It’s reusable: Once your code is modular and decoupled it becomes much easier to reuse parts of it.
Contrary to what a lot of people think, writing structured JavaScript is not more work than using inline code. There is a small penalty at the very beginning where the overhead of separating behaviour from content is noticeable, but at a certain point the relation reverses to mean that inline code is more work, and that threshold is very low.
JavaScript is a real programming language. Stop using it like it’s PHP and start using it to your own benefit.
15 JavaScript Development No-Nos
A few weeks ago Chad Fowler posted a list of 20 Rails No-Nos which included 20 of the things people in the Rails community considered to be “wrong”. I thought it would be interesting to compile a similar list for JavaScript code and came up with 15 points I myself consider “wrong”. The list is of course in no way canonical, I’m sure not everyone agrees with all the points or would like some others to be in there. I could only come up with 15, but I’m sure others can come up with some additional points in the comments :)
1) Global variables
Like in most programming languages, using global variables in JavaScript is very much a no-no. They cause unpredictable behaviour and hard-to-track-down errors and have the potential to wreak havoc with not just your own code but other people’s code too. There are several ways in which you can intentionally or accidentally create a global variable in JavaScript:
Creating a variable while in the global context
If you’re not inside a function definition you’re in the global context, and creating a variable here will make it global.
var foo = "global"; //Don't do this
function(){
var bar = "local"; //This is ok
}();
Not using the var keyword
Any time you assign a variable without using the var keyword, that variable becomes global:
function(){
foo = "global"; //Don't do this
var bar = "local"; //This is ok
}();
Assigning properties to the window variable
Properties assigned to the global window variable are the same as global variables:
function(){
window.foo = "global";//Don't do this
}();
function(){
return foo; //Returns "global"
}();
Creating global functions
Named functions in JavaScript are function objects assigned to variables. This means that whenever you do something like this…
function foo(){
//...
};
…you’re creating a global variable named foo.
If this article were limited to only one point it would be to always use var. This is the one thing that’s the easiest to remember and has the most significance when writing JavaScript code.
2) Inline JavaScript
Inline JavaScript is any JavaScript code that is mixed with HTML. There are two primary ways in which you can do this:
script tags
<form id="foo">
<!-- ... -->
</form>
<script>
$('foo').observe('submit', function(e){
if (!confirm("Are you sure?")) { e.stop(); }
});
</script>
onclick and friends
All HTML elements support at least some events using onfoo handler attributes, and additionally you can place javascript in a link element’s href tag:
<form id="foo" onsubmit="if (confirm("Are you sure?")) { return true; } else { return false; }">
<input type="text" value="Name" onclick="this.value='';" />
<a href="javascript:$('foo').submit()">Submit</a>
</form>
All of the above examples will work fine, but are for several reasons outdated and very poor techniques. For one, it’s just really messy; keeping HTML and JavaScript separate just makes everything a lot cleaner and easier to deal with. Secondly, they’re not reusable.. A better way would be to mark elements using IDs and class names, this way you could write the JavaScript once and reuse it just by adding class names to your HTML elements.
3) eval
Also like most other programming languages, using eval in JavaScript is considered.. well, evil. If you find yourself using eval, there’s a big chance your approach is wrong. The one exception to this is parsing JSON data, which is easily done using eval, but it’s much better to use a library for this, such as Douglas Crockford’s json2.js.
4) Old-style event handling
In the olden days, people would use two primary techniques to do event handling:
The good old onfoo HTML attributes:
<a href="#" id="foo" onclick="handleFooClick();">Click me</a>
And the slightly better JavaScript property functions using the same names:
var el = document.getElementById('foo');
el.onclick = function(){
handleFooClick()
};
Luckily things have improved since then and we can make use of modern browsers’ more advanced event handling. This means we can keep our event handlers separate from the HTML and we can attach several handlers to each element, prevent the default action from being performed and make use of event capture and bubbling. Internet Explorer, of course, does things its own way, but libraries such as Prototype and jQuery take care of that for us:
//Prototype
el.observe('click', function(e){
e.stop(); //Prevent the default action
if (confirm("Are you sure?")) {
doSomething();
}
});
5) Using constructors for literal values
JavaScript has a few “literal” values that are shorthand for instantiating objects and adding properties to them. The most-often misused (or not-used) of these are the array and object literals. There’s no need to use the Array and Object constructors:
var list = new Array();
list.push(1);
list.push(2);
list.push(3);
var map = new Object();
map["foo"] = 1;
map["bar"] = 2;
The above can be written more succinctly using literals:
var list = [1,2,3];
var map = {foo: 1, bar: 2};
6) alert("In your face")
Most of the time, using the alert method is unnecessary. The most prevalent misuse of this method is for debugging purposes, but using tools such as Firebug and equivalents for other browsers is a much better and more efficient way to debug JavaScript code.
7) element.style
You can change an element’s style directly by changing properties on its style property:
el.style.backgroundColor = "#ff0000";
el.style.color = "#00ff00";
This is usually not a good idea because you want to keep the styling separate from the behaviour and changing an element’s style directly will override anything you’ve defined in your CSS. A much better way to do this is to assign class names to elements and leave the styling up to the stylesheet:
var el = $$('.post').first();
el.addClassName("marked");
.post.marked {
background-color: #ff0000;
color: #00ff00;
}
This way your JavaScript only has to decide if an element should be in a “marked” state or not, and the CSS can decide what that means in terms of styling. When you come back to this code a month later because the definition of “marked” has changed, you (or someone else!) will find what they’re looking for in the most logical place, which is in the CSS stylesheet and not hidden deep inside some JavaScript code. If your new colleague gets tasked with changing this, he’s likely to use Firebug to see what exactly happens when a post gets marked. He will then see that the “marked” class name gets added, and Firebug will additionally tell him exactly in which CSS file and on which line this rule is defined.
8) No namespacing
This is similar to the “no globals” rule because the easiest way to avoid polluting the global namespace is to create your own. A site will usually create one global variable which serves as its namespace:
var MySite = {};
MySite.Foo = Class.create(/* ... */);
This way the site will have all of its “stuff” in one place, and the only global variable created was the MySite variable. If you’re creating a class that’s of general use, it’s somewhat permissible to place this in the global scope. The Prototype library, for instance, does this with classes such as Hash and Enumerable, while other libraries such as YUI create only one global variable which contains everything in it.
9) window.open
Except for a few cases, this method should really not be used. The most egregious misuse of this method is to open an entire website in a new window with no address bar, toolbar or scrollbars.
10) window.location, document.href, window.status
These, and I’m sure many other, properties are often misused. The location and document.href properties are most often used to redirect the user to a different page. While this is not really ideal in most cases, there are some situations where it can be useful, but they should be used sparingly. The window.status property is just wrong, and I’m not even sure if it works in modern browsers, but if you see this being used in a script you can be sure the script is outdated.
11) document.forms, document.all, document.layers
Like window.status, these properties are relics from the past which should not be used today. They were used as shortcuts to get to various parts of the document, but there are much better ways to do that nowadays. To get to an element in the DOM today, you should use the getElementById and DOM access methods and properties on elements such as element.childElements and element.parentNode. The most recent browsers even lets you access arbitrary elements using XPath or CSS. If you’re using a library such as Prototype or jQuery, these include some very intuitive and easy to use methods for walking the DOM tree.
12) setTimeout and setInterval using strings
The setTimeout and setInterval functions allow you to execute a piece of code at a later point or at a set interval. They can take either a string which is evaluated as code or a function which is run directly. The preferred option is to use a function.
//This is wrong
setTimeout("element.addClassName('foo');", 100);
//This is right
setTimeout(function(){
element.addClassName("foo");
}, 100);
13) with
The with keyword allow you to execute a piece of code as if the global context was an arbitrary object:
var element = $('foo');
with (element) {
title = "foo"; //element.title = "foo"
getChildren().first().remove(); //element.getChildren.first().remove()
}
While this may save a few keystrokes it can cause more trouble than it’s worth. The most harmful side effect of using the with statement is the accidental creation of global variables, which we already know are evil. If you try to assign a property that the object (element from the example) doesn’t already have, this will evaluate to mean a variable name, which without the var keyword will be considered global.
14) document.write
Like window.open, this method is a relic from the web’s early days and should not be used. If you see this in a script, it’s likely that it’s a few years past its expiration date.
15) Not following naming conventions
The naming convention for variables and properties in JavaScript is as follows:
- Local variable:
mixedCase - Object property:
object.mixedCase - Instance method:
object.mixedCase() - Class property:
Class.mixedCase - Class method:
Class.mixedCase() - Class name:
CamelCase - Namespace object:
CamelCase, orCAPSin some cases
While not following these conventions is not going to break your script, it’s going to make it harder for others to read and understand. In particular, constructor functions (classes) should use an uppercase first letter to distinguish it from other functions. This is also the convention if your classes are properties of a namespace object, as they should be most of the time: new MySite.ClassName().
Namespacing your code in an object
August Lilleaas says:
Namespacing your code in an object
Disclaimer: This is the (and my) first post/question, and it’s more of a suggestion or finished article than a question or problem that people can comment on. Now, on with the show!
You can use a shotgun blast of local functions.
var showCalendar = function(date){
// ...
}
var daysInMonth = function(){
// ...
}
var firstDayOfMonth = function(){
// ...
}
You can also tuck your code in an object. This cleans up things quite a bit.
Calendar = {
show: function(date){
// ...
},
daysInMonth: function(){
// ...
},
firstDayOfMonth: function(){
// ...
}
}
Calendar.show(new Date())
In addition to providing a clean namespace, an object also gives you this, which is very useful. You can use that to refer to variables and set states from inside the object.
Calendar = {
init: function(date, argetDomId){
this.date = new Date()
this.target = document.getElementById(targetDomId || "default")
this.width = 200
this.height = 500
},
render: function(){
// use this.width, this.target etc here
}
setDate: function(newDate){
this.date = newDate
},
firstDayOfMonth: function(){
// ...
}
}
Calendar.init(new Date())
Calendar.init(new Date(2007, 1, 24), "myCalendarTarget")
In this first submission, August presents not just one but two essential patterns that should be used in JavaScript development:
Namespacing: One of the big dont’s in JavaScript development is “don’t pollute the global namespace”. I.e. create as few global variables as you possibly can. The way to achieve this is simple: Create one global object which serves as a namespace for all your other objects. Namespacing is important in most programming languages, but in the unpredictable environment in which JavaScript runs, it becomes essential to keep interference to a minimum, because you simply don’t know what other code might be in use on the same page.
Encapsulation: JavaScript is an object-oriented language, and we should take advantage of that. Objects have internal state, which means you can put everything related into a black box which knows how to deal with its internals. Outside code only interacts with the object through its public interface, thus reducing coupling.
Welcome to The JavaScript Way
As you can probably tell, it’s directly inspired by The Rails Way which has been doing a great job of sharing best practises and ideas in the Rails world. We want to extend that to the very closely related JavaScript world because we think there are lots of brilliant ideas out there that should be shared among a wider audience. The building blocks of scalable, structured, modular and efficient JavaScript and Ajax applications are many, but so far it seems they’re not widely used, and we aim to change that!
If you have a question/problem/anything you want to ask about or share, just log in with your OpenID to the right and you’ll be able to submit it!