CommonJS in practice

Organizing your application code using CommonJS modules is a best practice in Titanium development. CommonJS is a popular specification for creating reusable JavaScript modules and has been adopted by several major platforms and frameworks such as Node.js and MongoDb.

CommonJS modules help solve JavaScript scope problems, placing each module within its own namespace and execution context. Variables and functions are locally scoped to the module, unless explicitly exported for use by other modules.

In addition to assisting with JavaScript scope concerns, CommonJS provides a pattern to expose a public stable interface to program against. The information-hiding design pattern allows module developers to update the internals of the module without breaking the public contract or interface. The ability to maintain a stable public interface in JavaScript is the key part of writing maintainable code that will be shared across apps and teams.

Titanium has implemented CommonJS in a similar fashion to Node.js in that you use the require method to return a JavaScript object, with properties, functions, and other data assigned to it, which form the public interface to the module.

The following screenshots illustrate the example app used to demonstrate the CommonJS high-level concepts that will be used throughout the book.

CommonJS in practice

Getting ready

Adding the CommonJS modules used in this recipe is straightforward and consists of copying the datahelper.js and dateWin.js files into the root of our Titanium project as shown in the following screenshot:

Getting ready

How to do it...

The following recipe illustrates how to use CommonJS to create both UI and Tools modules. In the following example, a simple app is created, which allows the user to increase or decrease the date by a day.

Creating the project's app.js

In our app.js we create our application namespace. These namespace variables will be used to reference our CommonJS modules later in the example.

//Create our application namespace
var my = {
  ui:{
    mod : require('dateWin')
  },
  tools:{},
  controllers:{}
};

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.

Ti.UI.Window is then created using the my.ui.mod already added to our app namespace. The open method is then called on our win object to open our example app's main window.

my.ui.win = my.ui.mod.createWindow();

my.ui.win.open();

Building the datehelpers module

In the Resources folder of our project, we have a CommonJS module datehelpers.js. This module has the following code:

  1. The helpers method is created within the datahelpers module. This function is private by default until it is exposed using the exports object.
    var helpers = function(){
      var createAt = new Date();
  2. The createdOn method is added to the helpers function. This function returns the createAt variable. This function is used to provide a timestamp value to demonstrate how the module can be initialized several times. Each time a new session is created for the module, the createAt variable will display the newly initialized timestamp.
      this.createdOn = function(){
        return createAt;
      };
  3. The addDays method is added to the helpers function. This method increases the provided date value by the number of days provided in the n argument.
      this.addDays = function(value,n){
        var tempValue = new Date(value.getTime()); 
        tempValue.setDate(tempValue.getDate()+n);
        return tempValue;
      }
    };

The module.exports is the object returned as the result of a require call. By assigning the helpers function to module.exports, we are able to make the private helpers function publically accessible.

module.exports = helpers;

The dateWin module

Also included in the Resources folder of our project is a CommonJS module dateWin.js. The following code section discusses the contents of this module.

  1. Use the require keyword to import the datehelpers CommonJS module. This is imported in the mod module level variable for later usage.
    var mod = require('datehelpers'),
  2. The createWindow function returns Ti.UI.Window allowing the user to work with the recipe.
    exports.fetchWindow=function(){
  3. Next a new instance of the dateHelper module is created.
      var dateHelper = new mod();
  4. The next step in building the createWindow function is to set the currentDateTime module property to a default of the current date/time.
      var currentDateTime = new Date();
  5. The Ti.UI.Window object, which will be returned by the createWindow function, is then created. This will be used to attach all of our UI elements.
      var win = Ti.UI.createWindow({
        backgroundColor:'#fff'
      });
  6. The dateDisplayLabel is used to show the result of the datehelper module as the user increases or decreases the date value.
      var dateDisplayLabel = Ti.UI.createLabel({
        text:String.formatDate
        (exports.currentDateTime,"medium"),
        top:120, height:50, width:Ti.UI.FILL, 
        textAlign:'center', color:'#000', font:{fontSize:42}
      });
      win.add(dateDisplayLabel);
  7. The addButton is used later in this recipe to call the datehelper module and add days to the current module value.
      var addButton = Ti.UI.createButton({
        title:"Add Day", top:220, left:5, width:150, height:50
      });
      win.add(addButton);
  8. The subtractButton is used later in this recipe to call the datehelper module and reduce the date of the current module value.
      var subtractButton = Ti.UI.createButton({
        title:"Subtract Day", top:220, right:5, 
      	 width:150, height:50
      });
      win.add(subtractButton);
  9. The following code in the addButton and subtractButton click handlers shows how the AddDays method is called to increment the currentDateTime property of the module.
      addButton.addEventListener('click',function(e){
  10. The following line demonstrates how to increase the currentDateTime value by a single day:
        exports.currentDateTime = 
        dateHelper.addDays(currentDateTime,1);
  11. Update the dateDisplayLabel with the new module value.
        dateDisplayLabel.text = String.formatDate(
        exports.currentDateTime,"medium");
      });    
      subtractButton.addEventListener('click',function(e){
  12. The following code snippet demonstrates how to reduce the currentDateTime by a day:
        exports.currentDateTime = 
        _dateHelper.addDays(currentDateTime,-1);
  13. Update the dateDisplayLabel with the new module value.
        dateDisplayLabel.text = String.formatDate(
        currentDateTime,"medium");
      });
      return win;
    };

How it works...

Creating a module is easy. You simply add a JavaScript file to your project and write your application code. By default, any variables, objects, or functions are private unless you have added them to the module or exports objects. The module and exports objects are special JavaScript objects created by Titanium and returned by the require method.

Require

To use a CommonJS module you must use the globally available require function. This function has a single parameter through which you provide the path to your JavaScript module. The following line demonstrates how to load a CommonJS module called datehelpers.js located in the root of your Titanium project.

var myModule = require('datehelpers'),

Note

When providing the require function with an absolute path, Titanium will start from the Resources folder of your project.

Titanium has optimized the module loading process so that when a module is first loaded using the require method, it is then cached to avoid the need to re-evaluate the module's JavaScript. This approach significantly improves the load performance for modules which share common dependencies. It is helpful to keep in mind the JavaScript is not re-evaluated if you have the need to manage/alter the module on load.

Properties

Adding a property to your CommonJS module is easy. You simply attach a variable to the exports object.

The following snippet demonstrates how to create a basic CommonJS property with a default value.

exports.myProperty = "I am a property";

More complex object properties can also be created, for example, as follows:

exports.myObjectProperty = {
  foo:1,
  bar:'hello'
};

You can also use a private variable to set the initial property's value. This often makes for more readable and streamlined code.

Create a local variable reflecting your business need.

var _myObjectProperty = {
  foo:1,
  bar:'hello'
};

You can then assign the local variable to a property attached to the exports object, for example, as follows:

exports.myObjectProperty = _myObjectProperty

Note

Remember these are just properties on the exports JavaScript object and can be changed by any caller.

Functions

Creating public functions is easy in CommonJS, you simply add a function to the exports object. Create an Adddays method as part of the exports object. This function accepts a date in the value parameter and an integer as the n value.

exports.AddDays = function(value,n){

Create a new variable to avoid the provided value from mutating.

  var workingValue = new Date(value.getTime());

Increase the date by using the n value provided. This could be either a positive or negative value. Negative values will decrease the date value provided.

  workingValue.setDate(workingValue.getDate()+n);

Return the new adjusted value.

  return workingValue;
};

You can also assign an exports method to a privately scoped function. This can be helpful in managing large or complex functions.

Create a locally scoped function named addDays.

function addDays(value,n){
  var workingValue = new Date(value.getTime());
  workingValue.setDate(workingValue.getDate()+n);
  return workingValue;
};

The addDays function is then assigned to exports.AddDays exposing it to callers outside of the module.

exports.AddDays = addDays;

Note

Remember these are just methods on the exports JavaScript object and can be changed by any caller.

Instance object using module.exports

Titanium provides the ability to create a module instance object using module.exports. This allows you to create a new instance of the function or object attached to module.exports. This is helpful in describing one particular object and the instance methods represent actions that this particular object can take.

This pattern encourages developers to think more modularly and to follow the single responsibility principle as only one object or function can be assigned to the module.exports.

The following code snippets demonstrate how to create and call a module using this pattern:

  1. Using Titanium Studio, create the employee (employee.js) module file.
  2. Next create the employee function.
    var employee = function(name,employeeId,title){
      this.name = name;
      this.employeeId = employeeId;
      this.title = title;
      this.isVIP = function(level){
        return (title.toUpperCase()==='CEO'),
      }
    };
  3. Then assign the employee function to module.exports. This will make the employee function publicly available to call using require.
    module.exports = employee;
  4. Using require, a reference to the module is created and assigned to the employee variable.
    var employee = require('employee'),
  5. Next the bob and chris objects are created using new instances of the employee object created earlier.
    var bob = new employee('Bob Smith',1234,'manager'),
    var chris = new employee('Chris Jones',001,'CEO'),
  6. Finally, the properties and functions on the bob and chris objects are called to demonstrate each object's instance information.
    Ti.API.info('Is ' + bob.name + ' a VIP? ' + bob.isVIP());
    Ti.API.info('Is ' + chris.name + ' a VIP? ' + chris.isVIP());

CommonJS global scope anti-pattern

The CommonJS implementation across Android and iOS is largely the same with one major scoping exception. If you are using a version of the Titanium framework below version 3.1, Android scopes all variable access to the module itself, while iOS allows the module to access objects outside of the module already loaded in the execution context. This should be considered an anti-pattern as it breaks many of the encapsulation principles the CommonJS specification was designed to prevent.

In Titanium Version 3.1, the decision has been made to deprecate global scope access in both iOS and Android in favor of the new Alloy.Globals object. You can read more about the Alloy.Globals object at http://docs.appcelerator.com/titanium/latest/#!/api/Alloy.

The following recipe demonstrates this common anti-pattern and highlights the CommonJS implementation differences in this area between iOS and Android.

//Create our application namespace
var my = {
  tools: require('scope_test'),
  session:{
    foo: "Session value in context"
  }
};

The testScope method is called on the tools module. This demonstrates how CommonJS module scope anti-pattern works.

my.tools.testScope();

The tools module containing the testScope method is part of this recipe's code and can be found in the scope.js file root of our project. This module contains the following code:

exports.testScope = function(){
  Ti.API.log
  ("Test Module Scope - Foo =  " + my.session.foo);
  return my.session.foo;
};

The scope-access anti-pattern is shown when calling the my.tools.testScope() method. In iOS, my.tools.testScope() returns "Session Value in context", because it has access to my.session.foo from the current execution context. In Android, prior to Titanium SDK Version 3.1, undefined object used to be returned as the module did not have access to the my.session.foo object. In the Titanium SDK Version 3.1 and greater, Android now returns "Session Value in context" as it has access to the my.session.foo object.

Access to global scope has been deprecated on both platforms starting with Titanium SDK Version 3.1 and will be removed in a future version of the SDK. If you have previously implemented this anti-pattern, corrective action is recommended as the deprecation of this feature will cause breaking changes within your app.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.98.34