In the previous section, we explored the key features of Knockout. We learned the basics of declarative data binding, automated UI refresh, dependency tracking, and templating. Knockout does a really good job of simplifying web application development by providing these features. However, it does not solve the problem of bringing structure to your JavaScript code.
Unlike an object-oriented programming language such as Java or C#, JavaScript does not enforce any particular structure. This is both a blessing and a curse. Blessing in the sense that you can bring your own rules on how to structure your code. This gives you power and flexibility. It can be a curse if you do not follow any structure as in that case, your code base becomes too large and complex. Giving structure to your JavaScript code becomes more and more important as you write complex JavaScript applications. Structuring your JavaScript will make the code more maintainable and readable. It also helps to make the code more testable.
An elegant yet simple way of giving structure to your JavaScript code is by using the module pattern. It is important to understand the basic concepts behind the module pattern as we will be using this pattern throughout this book. Let's get started with the basic concept.
Central to the module pattern is the concept of a module. A module is a component that encapsulates everything that is required to accomplish a set of related tasks. This includes data as well as behavior. Here is an example of creating a module using the module pattern in JavaScript:
(function () { /* module code */ }) ( );
Let's deconstruct and explore what the preceding code does. Since JavaScript does not provide a construct for creating modules or classes, we use the next best thing—the anonymous function construct. The preceding code constructs and executes an anonymous function. The module code inside this function maintains privacy from the outside world. This is because creating a function creates a new scope. The module code also maintains its state throughout the life cycle of the module. Notice the parenthesis ( )
at the end of our function. These parenthesis execute our anonymous function straight after creation and creates our module.
We need a way to namespace our newly created module. This will allow us to access any public attributes that the module might expose:
var Module = (function () { /* module code */ })( );
In the module we defined here, the scope of any attributes or function is confined to the module. You cannot access an attribute that is declared within the module. This is exactly what we want to do—encapsulate everything related to a set of tasks.
If everything is now encapsulated, how does the outside world interact with our module? The answer is a return object with references to attributes and functions that we want to expose to the outside world. With the public and private members defined, the module will look similar to this:
var Module = (function () { /* private attribute */ var privateAttribute; /* private function */ var privateFunction = function () {}; /* public attribute */ var publicAttribute; /* public function */ var publicFunction = function () {}; /* return object with reference to public attributes and functions */ return { publicAttribute: publicAttribute, publicFunction: publicFunction }; })();
The scope of the private members, prefixed with the word private, is confined to the module. The public members, prefixed with the word public, are exposed to the outside world through the return object. The return object simply references the public members. We can now access the public members as follows:
Module.publicAttribute = 'foo'; Module.publicFunction();
One final element I want to add to my module is a function that initializes the module. Some people like to call it a constructor. Strictly speaking, a constructor is a function that creates an object. It can, however, contain initialization logic. This is not what our function will do. Our function will only initialize the module, hence I won't be calling it a constructor. You can choose any name for your initialization function. I like to call it init
. Let's add the init
function to our module:
var Module = (function () { /* private attribute */ var privateAttribute; /* private function */ var privateFunction = function () {}; /* public attribute */ var publicAttribute; /* public function */ var publicFunction = function () {}; var init = function() { /* Module initialization logic*/ }; /* fire the init function */ init(); /* return object with reference to public attributes and functions */ return { publicAttribute: publicAttribute, publicFunction: publicFunction }; })();
In the preceding example, we can see the init
function being declared. The scope of this function is private as it is not exposed by the return object. Declaring the function does not mean that our function will execute when the module is created. We execute the function by calling it after declaration:
/* execute the init function */ init();
On most occasions, we want to execute our init
function after the HTML is fully loaded by the browser and the DOM is ready. This is where jQuery comes handy. We can use a feature provided by jQuery to execute the init
function once the HTML is fully loaded and the DOM is ready. This is done by replacing the call init()
; with:
/* execute the init function once the DOM is ready */ jQuery(init);
Passing any function as an argument to the jQuery function executes it once the DOM is ready. In the preceding code, we pass the init
function as an argument to the jQuery function.
Now that we have learned the basic concepts behind the module pattern, let's declare the contact view model we used earlier as a module:
var ContactViewModel = (function () { var contact = { id: ko.observable(1), name: ko.observable('John'), phoneNumber: ko.observable(00001111) }; Var retrieveContact = function (){ /* logic to retrieve contact form server side data repository */ }; Var updateContact = function (newPhoneNumber){ /* logic to update the contact with new phone number */ }; var init = function() { /* Module initialization logic*/ }; /* execute the init function once the DOM is ready */ $(init); return { contact: contact, updateContact: updateContact }; })();
Our preceding module is referenced by ContactViewModel
. It has a contact model and functions to retrieve and update the contact. It also has an initialization function, which will be executed once the DOM is ready. The module exposes the contact model and the updateContact
function as public members to the outside world. The retrieve contact function remains private to the module.