One of the biggest challenges developer’s face when working with JavaScript is organizing their code into easy to manage, maintainable blocks of code. This article will step through the process to create a clean, simple, extensible JavaScript Library Framework.
Code libraries like Prototype and jQuery have undoubtedly simplified the coding experience for programmers around the world. However, I find the extension models thoroughly frustrating in their complexity. It is very difficult to find a reusable template for organizing code that interfaces well with these tools. Today’s post will address these pain points.
Before beginning lets review the expected capabilities from our JavaScript Library:
- Encourage a logical division of behaviors and functionality.
- Provide the capability to thoroughly test any component in the framework.
- Easily control which code components to include on a page.
- Have a simplified model for adding additional, custom capabilities.
- Be capable of working with or without an external framework (i.e. jQuery, Prototype, etc.).
- At a minimum encourage creating scripts that are in compliance with the Single Responsibility, Separation of Concerns (SoC) and Law of Demeter (LoD) Principles.
As an example, when reviewing JavaScript code I find the AJAX calls mixed into the same module as html management code. In most cases the module needs to be split up several times into refined, single-purpose modules. Granted, JavaScript doesn’t have a module mentality outright. Even frameworks like jQuery mix all of the code into sub-classes into a single file. While there are performance benefits to having all the code in a single file it does make debugging and code management more difficult.
Framework Strategy
I would like to suggest that we begin by making things less efficient. Treat JavaScript the same as C#, VB, Java or any other language. Divide the work into separate files. Once the code is complete have a .Net or Java class combine the needed JavaScript files into a single package for performance benefits. To keep it maintainable let’s aim to divide-and-conquer our code base!
Namespaces further assist with clearly separating the concerns of our code-base. By taking the time to logically group and organize the code into files and namespaces we can more easily manage the code. Therein, lies the rub! We need to have a clearly defined namespace that is capable of crossing multiple files. jQuery addresses the Separation of Concerns (SoC) by using extensions although I don’t know many programmers who enjoy implementing the jQuery extensibility method. A quick review of online articles reveals frustration with how to extend jQuery.
We will resolve these issues by creating a library that is first-and-foremost an extensible framework. Then, any additional capability that needs to be implemented can be placed within it’s own file while attaching itself to the library through an extension.
The Core
Begin by creating the skeleton of the heart of our new extensible Library:
(function (wHnd) { var _CreateNS = function (n, t) { }; var _Extend = function (n, o, t) { }; });
Only two functions are required to gain the desired benefits of a fully-functional, extensible framework:
- _CreateNS manages the creation of our library’s namespaces.
- _Extend manages adding classes, functions and properties to the library.
With these methods our library is completely extensible with the ability to easily modularize the code-base into separate files. Furthermore, our solution will be in compliance with the Visual Studio 2012 intelli-sense capabilities. The new library will be in full compliance with small, testable, scalable work units fitting nicely into the Agile programming model.
Create a Custom Namespace
The _CreateNS method will be responsible for breaking the namespace into individual names creating each part until the whole has been completed. The first part of the name will be added to the target object (if a target object is missing the window object will be used instead). Then the target will be reassigned to the newly created child namespace node and we will add the next name to the namespace. This process continues until all parts of the namespace are created. Each namespace part will be an empty JSON object. The JSON object permits us to add additional namespace parts, classes, function or properties later as needed.
var _CreateNS = function (n, t) { if (!t) t = wHnd; //Ensure that we have a target object to add the namespace to. var nsItem, nsList = n.split("."); //Break the namespace into parts for (var i = 0, l = nsList.length; i < l; i++) { nsItem = nsList[i]; //Get the next available name part from the namespace if (!t[nsItem]) t[nsItem] = Object.create(null); //Add the name part when missing from the namespace t = t[nsItem]; //Select the new namespace child } return t; //Return the leaf namespace part to the caller };
Adding a Behavior, Property or Class to the Namespace
The _Extend method is responsible for creating the new class, property or method. Three arguments are accepted into the method: the name of the class, property or method; the actual value, function or object; and the target namespace or target object to extend. In the event that the name argument includes the namespace the method will ensure that the namespace exists prior to adding the new class, property or method.
var _Extend = function (n, o, t) { if (!t) t = wHnd; //Ensure that we have a target object to add the namespace to. if (n.indexOf(".") >= 0) { var t2 = n.substr(0, n.lastIndexOf(".")); //Extract the namespace from the name. t = _CreateNS(t2, t); //Append the namespace to the target getting a reference to the new namespace leaf child n = n.substr(n.lastIndexOf(".") + 1); //Remove the namespace from the name. } //Add the new behavior, property or object to the target namespace or target object. if (n && t.prototype && !t.hasOwnProperty[n]) t.prototype[n] = o; //Add the member for public access (when supported) if (n && !t[n]) t[n] = o; //Add the member for static access };
The _Extend method automatically defines the scope of the new property or method. The _Extend method attempts to create both a static and public version of the property or method. Static (or, “shared” in VB lingo) implies that the method is available only from the raw object; not the instantiated class. A public method is not available until after the object has been instantiated.
The following example demonstrates when a method is available:
//Create Object Adding Methods var n = function () { }; n["sayHi"] = function () { alert("Hi!"); } n.prototype["sayBye"] = function () { alert("Bye!"); } //Test methods n.sayHi(); //Static method works! n.sayBye(); //Public method doesn't exist. var n2 = new n(); n2.sayHi(); //Static method doesn't exist. n2.sayBye(); //Public method works!
Note that the public option may not always be available depending upon the type of target object to update. If the target object is a singleton object (JSON object or closed function) then prototype will not be available. In the event that you require more fine-grained control as to when a member is static or public simply break the _Extend method up into _ExtendPublic function and _ExtendStatic function. Alternatively you could modify the function to add the static method call only when you can’t add the public version because prototype is unavailable.
One last thing to mention. The _Extend method has been written to avoid overwriting an existing class, method or property. Optionally, a force argument could be added to the _Extend method so that the system would replace the class, method or property even when it already exists.
Exposing the Behaviors
At this point we have private functions within our library that aren’t of much use. So our next step will be to expose our functions. The beauty of our new framework is that we can use the framework to expose the functions!
_Extend("MyCustomLibrary.Reflector.CreateNS", _CreateNS); _Extend("MyCustomLibrary.Reflector.Extend", _Extend);
The Completed Core
So our final and complete solution is a mere two functions!
(function (wHnd) { var _CreateNS = function (n, t) { if (!t) t = wHnd; //Ensure that we have a target object to add the namespace to. var nsItem, nsList = n.split("."); //Break the namespace into parts for (var i = 0, l = nsList.length; i < l; i++) { nsItem = nsList[i]; //Get the next available name part from the namespace if (!t[nsItem]) t[nsItem] = {}; //Add the name part when missing from the namespace t = t[nsItem]; //Select the new namespace child } return t; //Return the leaf namespace part to the caller }; var _Extend = function (n, o, t) { if (!t) t = wHnd; //Ensure that we have a target object to add the namespace to. if (n.indexOf(".") >= 0) { var t2 = n.substr(0, n.lastIndexOf(".")); //Extract the namespace from the name. t = _CreateNS(t2, t); //Append the namespace to the target getting a reference to the new namespace leaf child n = n.substr(n.lastIndexOf(".") + 1); //Remove the namespace from the name. } //Add the new behavior, property or object to the target namespace or target object. if (n && t.prototype && !t.hasOwnProperty[n]) t.prototype[n] = o; //Add the member for public access (when supported) if (n && !t[n]) t[n] = o; //Add the member for static access }; _Extend("MyCustomLibrary.Core.CreateNS", _CreateNS); _Extend("MyCustomLibrary.Core.Extend", _Extend); });
So that’s our first file! Let’s call this file “MyCustomLibrary.Core.js”
Extending our Library
Next we will extend our custom library with a new module and behavior. Create a new file called, “MyCustomLibrary.Example.js” adding the following code.
/// <reference path="MyCustomLibrary.Core.js" /> (function (wHnd) { MyCustomLibrary.Core.Extend("MyCustomLibrary.Example.SayHi", function () { alert("Hi!"); }); MyCustomLibrary.Core.Extend("MyCustomLibrary.Example.SayBye", function () { alert("Bye!"); }); }); //Test extension to library by calling the new module's classes! MyCustomLibrary.Example.SayHi(); MyCustomLibrary.Example.SayBye();
If you are using Visual Studio 2012 or later open both the MyCustomLibrary.Core.js and MyCustomLibrary.Example.js files. Because of the reference path instruction you will be able to use intelli-sense with your newly created library! You should note that both files must be open in Visual Studio 2012 or the intelli-sense will not work.
Aliasing Namespaces
As the library namespace grows deeper it will become cumbersome to type in the entire namespace. Aliases may be created to reduce the typing required.
//Test extension to library by calling the new module's classes! var e = MyCustomLibrary.Example; e.SayHi(); e.SayBye();
Summary
Today’s article demonstrated how easily we can make a modular, extensible JavaScript Library. With the methods shown you can easily split up your JavaScript/jQuery/Prototype code into modules that can be used in combination as needed.
For Additional Information
While validating some of the concepts that I wanted to convey I discovered a wonderful article from Kenneth Truyers! Kenneth has done an excellent job provding a detailed explanation of the approach suggested in today’s article. For further details and constraints of the suggested JavaScript framework read Kenneth’s, JavaScript Namespaces and Modules article! Hat’s off to a well-written article, Kenneth!
Happy Coding!
Reblogged this on Sutoprise Avenue, A SutoCom Source.
Comment by SutoCom — August 18, 2013 @ 7:52 am |