Today I would like to describe how you can make your JavaScript code much much much better.
We know a lot about how to make our c# code much better. And we always use it.
We split out our c# code to classes, put the classes to modules, put the modules to layers, etc.
But we never do the same for our JavaScript code. And this is a big mistake.
And that's why we have a lot of
Spaghetti Code inside our apps.
The main problems with function spaghetti code are:
- It's really hard to figure out where the one block of code begins and where the other ends
- Who's responsible for what?
- How are we deal with global scope pollution (e.g. use the same variables in different pieces of code)
- Low code re-use
- Not easy to maintain, test and debug.
I'm not going to describe in details what the functional spaghetti code is, because you can find a lot of references in the Internet.
I would like to show here how to avoid it and make your code better using RequireJS.
Function spaghetti code
Let's start with a simple example of spaghetti code. (I can't say that it is really good example of horrible spaghetti code, but it is simple, so it's easy to understand and see all the changes)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Phase 1</title>
</head>
<body>
<div>
<h1>Modular Demo 1</h1>
</div>
<div id="messagebox"></div>
<script src="../Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
var baseUrl = '/api/messenger/';
function ShowMessage(id) {
$.ajax({
url: baseUrl + id,
type: 'GET',
dataType: 'json',
success: function(data) {
$("#messagebox").html(data);
}
});
}
var id = 55;
ShowMessage(id);
</script>
</body>
</html>
As you can see it's really simple example. The code sends ajax request to get a message from server and then puts this message to div container.
This code is correct and works well. And at the same time not perfect but is an example of spaghetti code.
All of your code logic (sending the request, putting a message to a div container) located in the same function.
I would like to repeat again, that it is not perfect example, but we need really simple one to better undersand how we can improve it.
JavaScript modules
First of all I would like to describe how you can use the module pattern to create a module in JavaScript.
Let's start with an example first.
var messenger = (function ($) {
var text = 'I am a module',
showMessage = function() {
$("#messagebox").html(text);
};
return {
showMessage: showMessage
};
})($);
That's it. You have a module - messenger.
This module has internal part:
text = 'I am a module',
And exposes externally function:
return {
showMessage: showMessage
};
So, for the other code only showMessage will be visible.
And it is really simple to use it:
messenger.showMessage();
Also in this example our module depends on jQuery. You can see it here:
var messenger = (function ($) {
. . .
})($);
As you can see it's really simple to create a module in JavaScript.
Let's see how we can re-factor our code.
Ravioli code
When you eating the ravioli you are totally sure where is the one ravioli and where is another one.
Also, to take SRP (single responsibility principle) into account we have to separate our code into modules which have only single responsibilities.
This is the result:
var config = (function() {
var baseUrl = '/api/messenger/';
return {
baseUrl: baseUrl
};
})();
var dataservice = (function($, config) {
var callApi = function (url, type, callback) {
$.ajax({
url: url,
type: type,
dataType: 'json',
success: function (data) {
callback(data);
}
});
},
getMessage = function (id, callback) {
url = config.baseUrl + id;
callApi(url, 'GET', callback);
};
return {
getMessage: getMessage
};
})($, config);
var messenger = (function ($, dataservice) {
var showMessage = function(id) {
dataservice.getMessage(id, function(message) {
$("#messagebox").html(message);
});
};
return {
showMessage: showMessage
};
})($, dataservice);
(function (messenger) {
var id = 55;
messenger.showMessage(id);
})(messenger);
As a result we have 4 modules. Let's describe them:
- config module - for storing our global variables
- dataservice module - for doing communication with the server (sending ajax requests and getting the response back)
- messenger module - for showing a message (placing a message into containers)
- main module - as starting point of our app
And it's really easy now to change our modules if needed, for example if we will decide to change our communication mechanism with the server, or to change our messenger to show a message into jQuery dialog.
Split out your modules
The next step is splitting your modules out into JavaScript files.
Because it is not really good solution to place your JavaScript code into HTML.
At the end you will have four JavaScript files.
config.js
var config = (function() {
var baseUrl = '/api/messenger/';
return {
baseUrl: baseUrl
};
})();
dataservice.js
var dataservice = (function($, config) {
var callApi = function (url, type, callback) {
$.ajax({
url: url,
type: type,
dataType: 'json',
success: function (data) {
callback(data);
}
});
},
getMessage = function (id, callback) {
url = config.baseUrl + id;
callApi(url, 'GET', callback);
};
return {
getMessage: getMessage
};
})($, config);
messenger.js
var messenger = (function ($, dataservice) {
var showMessage = function(id) {
dataservice.getMessage(id, function(message) {
$("#messagebox").html(message);
});
};
return {
showMessage: showMessage
};
})($, dataservice);
main.js
(function (messenger) {
var id = 55;
messenger.showMessage(id);
})(messenger);
Also, we have to change our HTML to load all of these JavaScript files.
index.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Phase 1</title>
</head>
<body>
<div>
<h1>Modular Demo 1</h1>
</div>
<div id="messagebox"></div>
<script src="../Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="config.js" type="text/javascript"></script>
<script src="dataservice.js" type="text/javascript"></script>
<script src="messenger.js" type="text/javascript"></script>
<script src="main.js" type="text/javascript"></script>
</body>
</html>
Finally, our code looks much better now.
Loading modules in proper order
Everything almost perfect in our code, except of loading scripts in the proper order.
Honestly, we do this task, but we do it manually.
Everything changes if you decide to order your modules in HTML files in alphabetical order.
<script src="config.js" type="text/javascript"></script>
<script src="dataservice.js" type="text/javascript"></script>
<script src="main.js" type="text/javascript"></script>
<script src="messenger.js" type="text/javascript"></script>
I've just put the module main.js above messenger.js. And unfortunately it has really changed behavior of our app.
Instead of showing a message it shows an exception now.
Line: 1
Error: 'messenger' is undefined
It has been happened because we changed the order, and module main.js has been loaded before messenger.js. As a result module main.js tries to call messenger.showMessage(), but the messenger is undefined, because it has not been loaded yet.
Of course, in this simple example we can easily control the order of the four scripts, but in a real application we can manage 30, or 50 or even more modules.
And it becomes a serious problem.
In this case RequireJS can really helps.
RequireJS
The following command in the Package Manager console will install
RequireJS package into your ASP.NET application:
PM > Install-Package RequireJS
RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.
In other words RequireJS really helps:
- To define our modules
- To resolve module dependencies
- To load scripts in the proper order (and asynchronously)
So, RequireJS really helps to define a structure to the modules in a JavaScript applications.
RequireJS modules
First I would like to show how you can create modules using RequireJS.
I will use the same example as I used to show how to create a JavaScript module.
define('messenger',
['jquery'],
function ($) {
var text = 'I am a module',
showMessage = function() {
$("#messagebox").html(text);
};
return {
showMessage: showMessage
};
}
);
It looks as easy as a JavaScript module. But I would like to describe some differences.
The RequireJS module starts with:
define('messenger',
Where 'messenger' is the module ID. You can use this ID if you want to reference this module in other modules.
The next line describes dependencies of this module:
['jquery'],
In this example our module depends on jQuery only.
And then you have to specify module body as a function.
As you can see it's really simple to create a module using RequireJS.
Using RequireJS
Let's change all of our modules.
config.js
define('config',
[],
function () {
var baseUrl = '/api/messenger/';
return {
baseUrl: baseUrl
};
}
);
dataservice.js
define('dataservice',
['jquery', 'config'],
function ($, config) {
var
callApi = function (url, type, callback) {
$.ajax({
url: url,
type: type,
dataType: 'json',
success: function (data) {
callback(data);
}
});
},
getMessage = function (id, callback) {
url = config.baseUrl + id;
callApi(url, 'GET', callback);
};
return {
getMessage: getMessage
};
}
);
messenger.js
define('messenger',
['jquery', 'dataservice'],
function ($, dataservice) {
var showMessage = function (id) {
dataservice.getMessage(id, function (message) {
$("#messagebox").html(message);
});
};
return {
showMessage: showMessage
};
}
);
main.js
(function() {
requirejs.config(
{
paths: {
'jquery': '../Scripts/jquery-1.8.2.min'
}
}
);
require(
['messenger'],
function(messenger) {
var id = 55;
messenger.showMessage(id);
}
);
})();
All of the modules look as they were before, except of main module.
In this module I have configured RequireJS to specify where RequiteJS can find the jquery module.
requirejs.config(
{
paths: {
'jquery': '../Scripts/jquery-1.8.2.min'
}
}
);
And then, I've specified the start up code, which depends on messenger module:
require(
['messenger'],
function(messenger) {
var id = 55;
messenger.showMessage(id);
}
);
After all, we have to change our HTML to load our modules.
index.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Phase 1</title>
</head>
<body>
<div>
<h1>Modular Demo 1</h1>
</div>
<div id="messagebox"></div>
<script data-main="main" src="../Scripts/require.js" type="text/javascript"></script>
</body>
</html>
Small remarks about our HTML changes.
We should not load any of our modules or jQuery, because RequireJS will handle it for us.
We have to load RequireJS only, and specify 'data-main' attribute, which tells RequireJS to load main.js script after RequireJS loads. In other words, we specify start up script in 'data-main' attribute.
And after all RequireJS does the 'magic' and loads all of our modules in proper order automatically.
As a result, our code becomes much much better now, as I promised at the beginning of this post.
That's all. And see you next time.