Introduction
Unobtrusive JavaScript is the way of writing JavaScript language in which we properly separate Document Content and Script Content thus allowing us to make a clear distinction between them. This approach is useful in so many ways as it makes our code less error prone, easy to update and to debug.
Target Audience
This article is intended for those who have intermediate to advanced level of understanding in writing JavaScript code. There are some APIs and features that might be new for some developers and I have tried to describe them separately from the code.
The Concept
The basic concept of unobtrusive programming is that JavaScript should be used as an enhancement to our web page instead of an absolute requirement. If you do not need JavaScript, then don't use it; your static content will display just fine with plain old HTML and CSS. So many developers make the mistake of importing code libraries before even figuring out if they will be even required. jQuery for example has been so much mis-used when all our basic needs can be easily fulfilled by CSS and pure JavaScript code.
If your application is dynamic and needs to call server code asynchronously, update HTML content and do all sorts of event binding, then by all means, do use JavaScript, and libraries like jQuery, etc. but try to code them in a separate layer systematically so that the HTML content stays clean and semantically separated.
Following are the areas in web application development which have a clear positive impact when we write JavaScript unobtrusively:
Code Separation
JavaScript code is not embedded into HTML, thus making it easy to update or upgrade the code.
Stability
Unobtrusive JavaScript generates less error messages. The web page will always display all the content from the static markup and CSS even if the script fails to run.
Graceful Degradation and Progressive Enhancement
If any feature is not supported by the browser, then the code should silently turn off that feature instead of throwing an error message. Similarly, if more advanced versions of a feature are available, the code should use the upgraded features to give the user an experience as per the browser advancement.
Cleaner Global Scope
The global scope or the window scope will stay clean of unnecessary or unwanted objects, properties, functions and variables. The unobtrusive way is to have a single point of entry into the application's data using namespaces.
Using the Code
This article is focused less on the theoretical side of unobtrusive JavaScript programming and paying more attention to the practical side; areas of code where it applies the most. In the next section, there are code snippets which will show the Obtrusive Way and the Unobtrusive Way of writing code. The core idea is to separate JavaScript from the HTML and later to extend this approach when altering the DOM from the JavaScript.
Before we start comparing the Good code and the Bad code, I would like to make this very clear that this approach should not be pushed too far in use so as to make the life of other developers very difficult in doing changes to the codebase. In the end, we all agree that errors and bugs arise and anyone should be able to fix them in the least possible time; our unobtrusive code should not come in the way of doing that.
Let's start looking at the code snippets which are used commonly where we can apply the unobtrusive coding practice.
HTML Anchors
It is a very common practice to wire-up the anchor's click event in its HTML markup declaration. This can pose many problems; like difficulty in debugging the code and moreover we will need to modify the anchor tag if we need to change the JavaScript code for any reason.
<body>
Search Engines
<br />
<a href="http://www.google.com" onclick="window.open(this.href);return false;">Google</a>
<a href="http://www.search.yahoo.com" onclick="window.open(this.href);return false;">Yahoo Search
</a>
<a href="http://www.bing.com" onclick="window.open(this.href);return false;">Bing</a>
</body>
The solution is to go unobtrusive here by separating HTML and Script code.
Unobtrusive Way
<body>
Search Engines
<br />
<a href="http://www.google.com">Google</a>
<a href="http://www.search.yahoo.com">Yahoo Search</a>
<a href="http://www.bing.com">Bing</a>
<script>
document.addEventListener('DOMContentLoaded', function () {
var anchors = document.getElementsByTagName('a');
WireupAnchors.bind(null, anchors)();
});
function WireupAnchors(anchors) {
anchors = anchors || [];
for (var i = 0; i < anchors.length; i++) {
iterator.call(anchors[i]);
}
function iterator() {
this.onclick = function () {
window.open(this.href);
return false;
}
}
}
</script>
</body>
In the above code, DOMContentLoaded
event is fired when the document content is parsed and all the HTML has been loaded. This does not include stylesheets, external CSS and image files.
When the DOM is ready, then WireupAnchors
will get called. The anchors are being passed as an array to WireupAnchors and for each item iterator
will be called to bind the click event of the anchor with an anonymous function to open a new window. The href
attributes's value is used as the URL.
Event Binding
The following code is more or less same as the above, the click
event binding is done inline of the button declaration.
<body>
<input type="button" value="Button1" onclick="button1_Click(); return false;" />
<input type="button" value="Button2" onclick="button2_Click(); return false;"/>
<script>
function button1_Click() {
alert('Button1 was Clicked!');
}
function button2_Click() {
alert('Button2 was Clicked!');
}
</script>
</body>
Unobtrusive Way
Instead of using inline code, there are specific IDs assigned to the buttons and event binding is done through separate JavaScript code which is not mixed up with the HTML.
<body>
<input id="button1" type="button" value="Button1" />
<input id="button2" type="button" value="Button2"/>
<script>
document.addEventListener('DOMContentLoaded', WireUpEvents);
function WireUpEvents() {
var button1 = document.getElementById('button1'),
button2 = document.getElementById('button2');
button1.addEventListener('click', button1_Click);
button2.addEventListener('click', button2_Click);
}
function button1_Click() {
alert('Button1 was Clicked!');
return false;
}
function button2_Click() {
alert('Button2 was Clicked!');
}
</script>
</body>
In the above code, addEventListener
is simply being used to bind the click event of the buttons
Setting CSS Properties
Whenever we need to set the HTML styling for any control using JavaScript, then our first thought of action is to set specific CSS property values. While there is no direct threat to the well being of our web application by doing this, the problem arises when we have to update the code. We may need to find all the occurences of the lines of code that need to be changed and that is a painfully time consuming process.
<body>
<div id="myDiv"></div>
<script>
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad() {
var myDiv = document.getElementById('myDiv');
myDiv.style.width = '100px';
myDiv.style.height = '100px';
myDiv.style.borderRadius = '5px';
myDiv.style.borderColor = 'black';
myDiv.style.borderWidth = '2px';
myDiv.style.borderStyle = 'solid';
myDiv.style.backgroundColor = 'green';
myDiv.style.display = 'block';
}
</script>
</body>
Unobtrusive Way
The solution to the above problem is to create specific CSS classes and then update the element's CSS class value directly instead of setting specific CSS properties. This approach will make code updates much easier and the code-base will be less prone to bugs.
<head>
<title></title>
<style type="text/css">
.GreenBox {
width: 100px;
height: 100px;
border-radius: 5px;
border: 2px solid black;
display: block;
background-color: green;
}
</style>
</head>
<body>
<div id="myDiv"></div>
<script>
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad() {
var myDiv = document.getElementById('myDiv');
myDiv.className = 'GreenBox';
}
</script>
</body>
GreenBox
is a CSS class in the <style>
tag which contains all the property values which were individually being set through the JavaScript code in the previous code snippet. In this code, we are simply setting the class name of the element.
Use of HTML Attributes
The following code is for setting a red border around the text-box if its empty, to show the user that the field is required. In the following code, there is a function RequiredFieldValidation
which is used commonly for all the fields. But the problem is that the change event is being wired-up for all the individual fields which makes our code unnecessarily long.
<head>
<title></title>
<style type ="text/css">
.RedBox{
padding:2px;
border:2px solid red;
border-radius:5px;
}
.BlackBox{
padding:2px;
border:2px solid black;
border-radius:5px;
}
</style>
</head>
<body>
<input id="txtFistName" type="text" placeholder="First Name" class="RedBox" />
<input id="txtLastName" type="text" placeholder="Last Name" class="RedBox" />
<br /><br />
<input id="txtEmail" type="text" placeholder="Email Id" class="RedBox" />
<br /><br />
<input id="txtPhone" type="text" placeholder="Phone" class="RedBox" />
<script>
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad() {
var txtFirstName = document.getElementById('txtFistName'),
txtLastName = document.getElementById('txtLastName'),
txtEmail = document.getElementById('txtEmail'),
txtPhone = document.getElementById('txtPhone');
txtFistName.addEventListener('change', RequiredFieldValidation.bind(txtFistName));
txtLastName.addEventListener('change', RequiredFieldValidation.bind(txtLastName));
txtEmail.addEventListener('change', RequiredFieldValidation.bind(txtEmail));
txtPhone.addEventListener('change', RequiredFieldValidation.bind(txtPhone));
}
function RequiredFieldValidation() {
this.className = (this.value.length === 0) ? 'RedBox' : 'BlackBox';
}
</script>
</body>
Unobtrusive Way
The above code can be improved by adding a common attribute to all the inputs which are needed to be validated by following same set of rules. Following code does that by having Required
attribute for the text inputs. In the following code we are binding RequiredFieldValidation
function with the change event of all the text-fields having the Required
attribute.
<head>
<title></title>
<style type ="text/css">
.RedBox{
padding:2px;
border:2px solid red;
border-radius:5px;
}
.BlackBox{
padding:2px;
border:2px solid black;
border-radius:5px;
}
</style>
</head>
<body>
<input id="txtFistName" type="text" placeholder="First Name" class="RedBox" Required/>
<input id="txtLastName" type="text" placeholder="Last Name" class="RedBox" Required/>
<br /><br />
<input id="txtEmail" type="text" placeholder="Email Id" class="RedBox" Required/>
<br /><br />
<input id="txtPhone" type="text" placeholder="Phone" class="RedBox" Required/>
<script>
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad() {
var required = document.querySelectorAll('[Required]');
for (var i = 0; i < required.length; i++) {
required[i].addEventListener('change', RequiredFieldValidation.bind(required[i]));
}
}
function RequiredFieldValidation() {
this.className = (this.value.length === 0) ? 'RedBox' : 'BlackBox';
}
</script>
</body>
The browsers which support Html5 provide an error message for the fields with required
attribute if the field is blank when the form is submitted. For more information on this, you can refer to the following link:
In the above code, required attribute is being extended to visually highlight the field with a red border. The code is mostly self explanatory, querySelectorAll
is being used to find all the elements with the required
attribute. For each element found, the code is binding the change
event with the RequiredFieldValidation
function.
Namespaces
The next code is for a small calculator application. You can easily notice that every variable and function is just lying there in the Global Scope. This approach is okay if we do not have external libraries imported into the code, but we know this rarely is the case in any decent web application. We have all kinds of external APIs to help us make better user interface and to write better business logic.
So we cannot have any little variable, property, function or object polluting the global scope because it might be overwriting some existing code implementation.
<body>
<input type="text" id="txtNumber1" /> <input type="text" id="txtNumber2" />
<br /><br />
<input type="button" value="Add" add/>
<input type="button" value="Subtract" subtract/>
<input type="button" value="Multiply" multiply/>
<input type="button" value="Divide" divide/>
<br /><br />
<input type="text" id="txtResult" />
<script>
var txtNumber1, txtNumber2, txtResult;
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad() {
txtNumber1 = document.getElementById('txtNumber1');
txtNumber2 = document.getElementById('txtNumber2');
txtResult = document.getElementById('txtResult');
document.querySelector('[add]').addEventListener('click', add_click);
document.querySelector('[subtract]').addEventListener('click', subtract_click);
document.querySelector('[multiply]').addEventListener('click', multiply_click);
document.querySelector('[divide]').addEventListener('click', divide_click);
}
function calculate(number1, number2, operationType) {
var result = 0;
switch (operationType.toLowerCase()) {
case 'add':
result = number1 + number2;
break;
case 'subtract':
result = number1 - number2;
break;
case 'multiply':
result = number1 * number2;
break;
case 'divide':
result = number1 / number2;
break;
}
return result;
}
function add_click() {
txtResult.value = calculate(parseFloat(txtNumber1.value)
, parseFloat(txtNumber2.value), 'Add');
}
function subtract_click() {
txtResult.value = calculate(parseFloat(txtNumber1.value)
, parseFloat(txtNumber2.value), 'Subtract');
}
function multiply_click() {
txtResult.value = calculate(parseFloat(txtNumber1.value)
, parseFloat(txtNumber2.value), 'Multiply');
}
function divide_click() {
txtResult.value = calculate(parseFloat(txtNumber1.value)
, parseFloat(txtNumber2.value), 'Divide');
}
</script>
</body>
Unobtrusive Way
The above problem has a very simple solution and that is to use namepsaces. A namespace in JavaScript is implemented using objects, and they provide us a single point of entry into our application's different modules. This approach eliminates the possibility of global scope pollution and its resulting side effects.
In the following code, App
object is being treated as the root namespace. Properties like Operations
, Inputs
, Events
etc are sub-namespaces which contain information specific to them.
<body>
<input type="text" id="txtNumber1" />
<input type="text" id="txtNumber2" /> <br /><br />
<input type="button" value="Add" add />
<input type="button" value="Subtract" subtract />
<input type="button" value="Multiply" multiply />
<input type="button" value="Divide" divide />
<br /><br />
<input type="text" id="txtResult" />
<script>
var App = { 'Operations': {}, 'Inputs': {}, 'Outputs': {}, 'Commands': {}, 'Events': {} };
App.Events = {
'Add': function () {
App.Outputs.Result.value = App.Operations.Calculate(
parseFloat(App.Inputs.Number1.value)
, parseFloat(App.Inputs.Number2.value), 'Add');
},
'Subtract': function () {
App.Outputs.Result.value = App.Operations.Calculate(
parseFloat(App.Inputs.Number1.value)
, parseFloat(App.Inputs.Number2.value), 'Subtract');
},
'Multiply': function () {
App.Outputs.Result.value = App.Operations.Calculate(
parseFloat(App.Inputs.Number1.value)
, parseFloat(App.Inputs.Number2.value), 'Multiply');
},
'Divide': function () {
App.Outputs.Result.value = App.Operations.Calculate(
parseFloat(App.Inputs.Number1.value)
, parseFloat(App.Inputs.Number2.value), 'Divide');
},
'Load': function () {
App.Inputs = {
'Number1': document.getElementById('txtNumber1'),
'Number2': document.getElementById('txtNumber2'),
};
App.Outputs = {
'Result': document.getElementById('txtResult')
};
App.Commands = {
'Add': document.querySelector('[add]'),
'Subtract': document.querySelector('[subtract]'),
'Multiply': document.querySelector('[multiply]'),
'Divide': document.querySelector('[divide]')
};
App.Commands.Add.addEventListener('click', App.Events.Add);
App.Commands.Subtract.addEventListener('click', App.Events.Subtract);
App.Commands.Multiply.addEventListener('click', App.Events.Multiply);
App.Commands.Divide.addEventListener('click', App.Events.Divide);
}
};
App.Operations.Calculate = function (number1, number2, operationType) {
var result = 0;
switch (operationType.toLowerCase()) {
case 'add':
result = number1 + number2;
break;
case 'subtract':
result = number1 - number2;
break;
case 'multiply':
result = number1 * number2;
break;
case 'divide':
result = number1 / number2;
break;
}
return result;
};
document.addEventListener('DOMContentLoaded', App.Events.Load);
</script>
</body>
The best thing about the above code snippet is that it is neatly organized into relevant sections of the namespace and each piece of information has its own namespace address.
Graceful Degradation
Graceful degradation simply means to silently fall back to the primitive version of a functionality if a particular feature is not supported or if the JavaScript is not available for use. The following code demonstrates the use of <noscript>
tag. The contents of this tag will be added to the web page if JavaScript is disabled.
<body>
<noscript>
JavaScript seems to be disabled!
<br />
</noscript>
<a href="www.codeproject.com">CodeProject</a>
</body>
Progressive Enhancement
Progressive enhancement is the art enhancing the user experience or code efficiency by utilizing most recent feature available. The best example can be seen in rounded corners which were previously unavailable to CSS and were hard to implement as a custom solution. Other examples can be seen in the new APIs that are available in the modern browsers.
In the following code example, click event is being wired-up by creating a delegate of button_click
function. For this purpose, Function.prototype.bind
is being used, but what happens if our code is running on an older browser which does not support bind(). To handle this scenario, we need to add our custom bind API to mimic the modern browsers and to make sure that our application will run, no matter which browser is used.
<body>
<input id="button1" type="button" value="Hello" />
<script>
var button1,
messages = {'Message1': 'Hello World'};
document.addEventListener('DOMContentLoaded', onLoad);
function onLoad(){
button1 = document.getElementById('button1');
button1.addEventListener('click', button_click.bind(messages));
}
function button_click() {
alert(this.Message1);
}
/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not
callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () { },
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
</script>
</body>
There can be so many other places where unobtrusive JavaScript programming can be applied. We should always be on the lookout for such areas where we can write code differently so as to improve the codebase and make it less prone to errors and bugs.
Final Words
The decision to follow unobtrusive coding practice depends on developer preference, but it never hurts to create our web application by following defensive measures. When our code runs on the internet, there can be a thousand ways for things to go wrong; such as an unidentified device, unsupported APIs, unwanted dynamic scripts etc. So it's always a good idea to follow certain standard coding practices, like the ones in this article to make our lives a little more easier to live.