Appcelerator Titanium Business Application Development Cookbook

By Benjamin Bahrenburg
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Patterns and Platform Tools

About this book

The demand for Mobile in the Enterprise has never been greater. While meeting this demand is becoming increasingly business critical, the complexities of development are amplified by the explosion in the number and variety of devices and platforms. Appcelerator's Titanium Mobile Platform stands out for its rapid development speed, large number of APIs, and focus on providing a native experience. This ability to rapidly provide business value in a cross-platform way makes Titanium a compelling platform for the creation of Enterprise applications.

Appcelerator Titanium Business Application Development Cookbook is your complete guide to creating business applications. From building a unique user experience to securing your application, this book covers every aspect of business application development.

Beginning with a discussion of design patterns, the practical recipes in this cookbook progress through different topics required for Enterprise cross-platform mobile development. Each recipe is a self-contained lesson that can be used in creating apps for your organization. With Appcelerator Titanium Business Application Development Cookbook learn how to work with data on the device, create charts and graphs, and interact with various web services. Later recipes discussing application security and native module development help provide additional resources to accelerate your next Titanium mobile development project.

Publication date:
June 2013
Publisher
Packt
Pages
328
ISBN
9781849695343

 

Chapter 1. Patterns and Platform Tools

In this chapter we will cover:

  • Defining an app namespace

  • CommonJS in practice

  • Using platform indicators

  • Global logging using Ti.App.Listener

 

Introduction


For many, building a Titanium app will be their first experience with a large, complete JavaScript project. Whether you are designing a small expense tracking system or a complete CRM tool, implementing proper design patterns in Titanium will improve the performance and maintainability of your app.

The cross-platform nature and underlying architecture of Titanium influences how many common design patterns can be implemented. In this chapter, we will demonstrate how to apply patterns to increase speed of development while implementing best practices for multiple device support.

Introducing Titanium

Appcelerator Titanium Mobile is a platform for building cross-platform native mobile applications using modern web technologies, such as JavaScript, CSS, and HTML. Titanium Mobile is an open source project developed by Appcelerator Inc and licensed under the OSI-approved Apache Public License (Version 2).

The Titanium Mobile project is one of the most active on Github with a large number of commits each day. The Github repository is the focal point for many in the community including module developers, app developers needing night builds, and individual contributors to the Titanium project.

The Titanium ecosystem is one of the largest in the industry with a community of more than 450,000 worldwide developers running apps on 10 percent of the world's devices. Appcelerator boasts one of the largest mobile marketplaces providing third-party components for Titanium Mobile platform.

Architecture of Titanium

Titanium is a module-based mobile development platform consisting of JavaScript and native platform code (Java, Objective-C, and C++). The architectural goal of Titanium is to provide a cross-platform JavaScript runtime and API for mobile development; this differs from other frameworks' approaches of building "native-wrapped" web applications.

Titanium uses a JavaScript interpreter to create a bridge between your app's JavaScript code and the underlying native platform. This approach allows Titanium to expose a large number of APIs, and native UI widgets without sacrificing performance. Titanium's UI controls are truly native and not visually emulated through CSS. Thus, when you create a Ti.UI.Switch, it is actually using the native UISwitch control on iOS.

Each Titanium application is organized into layered architecture consisting of the following major components:

  • Your JavaScript code: At compile time, this will be encoded and inserted into Java or Objective-C files

  • Titanium's JavaScript interpreter: On Android V8 or JavaScriptCore for iOS

  • The Titanium API: This is specific for a targeted platform created in Java, Objective-C, or C++

  • Native Custom Modules: A large variety of open source and commercial modules are available

At runtime, the Titanium SDK embedded within your app creates a native code JavaScript execution context. This execution content is then used to evaluate your app's JavaScript code. As your JavaScript is executed, it will create proxy objects to access native APIs such as buttons and views. These proxies are special objects that exist both in the JavaScript and native contexts acting as a bridge between the two environments.

For example, if we have a Ti.UI.View object and we update the backgroundColor to blue, the property is changed in JavaScript and then the proxy then updates the correct property in the underlying native layer as shown in the following diagram:

Building a Cross-platform

Titanium provides a high-level cross-platform API, however it is not a write once, run anywhere framework. When building cross-platform apps, it is recommended to adopt a "write once, adapt everywhere" philosophy. With Titanium you can add platform-specific code to handle each platform's different UI requirements, while keeping your business logic 100 percent cross-platform compatible.

Building best of breed cross-platform applications, Titanium provides tools to:

  • Identify the platform and model at runtime

  • Ability to handle platform-specific resources at build time

  • Apply platform and device-specific styling

In addition to platform tooling, the Titanium API is designed to assist with cross-platform development. Each major component such as Maps, Contacts, and FileSystem are separated into its own component namespaces under the top-level namespace called Ti or Titanium. These component namespaces then have their own child namespaces to segment platform-specific behavior.

An example of this segmentation is the Ti.UI namespace, which contains all UI components. This namespace contains common APIs such as Ti.UI.View and Ti.UI.TableView. Additionally, the Ti.UI namespace has platform-specific child namespaces such as Ti.UI.iPad containing controls such as Ti.UI.iPad.Popover. The same design applies to non-visual APIs such as Ti.Android, a namespace which contains Android-specific behavior.

 

Defining an app namespace


Using namespaces is important in Titanium app development, as it helps organize your code while not polluting the global namespace. The practice of creating variables and methods without being associated with a namespace or other scoping condition is called polluting the global namespace . Since these functions and objects are scoped globally, they will not be eligible for collection until the global namespace loses scope during application shutdown. This can often result in memory leaks or other unwanted side effects.

How to do it...

The following example shows how to create a namespace for our app in our app.js called my with three subnamespaces called ui, tools, and controllers.

var my = {ui:{},tools:{},controllers:{}}

As we build our recipes, we will continue to add functionality to the preceding namespaces.

 

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.

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:

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

 

Using platform indicators


Handling different devices, platforms, and models is often the biggest challenge with cross-platform development. Titanium provides the Ti.Platform namespace to help you make decisions in your code on how to handle runtime execution.

In the following recipes, we will walk through how to create a PlatformHelpers CommonJS module containing convenience methods to solve many of your platform-related queries. The following screenshots demonstrate this recipe while running on both the iPhone and Android platforms.

Getting ready

The Ti.Platform namespace is helpful for providing platform and device-specific details, but often you need a high level of detail such as when you have a tablet running your app and if so if it is an iPad Mini.

Adding recipe components to your project

Adding the PlatformHelpers module to your project is easy. Simply copy the platform_helpers.js file into your project as shown in the following screenshot:

How to do it...

The following app.js demonstrates the PlatformHelpers module described earlier in this chapter. This sample app presents a window with your device details. Give it a try on all of your Android and iOS devices.

  1. To create our same app, first we declare our app namespace and import our PlatformHelper module.

    //Create our application namespace
    var my = {
      platformHelpers : require('platform_helpers')
    };
    
    (function(){
  2. Next we create the window in which we will present the device information.

    var win = Ti.UI.createWindow({
      backgroundColor:'#fff'
    });
  3. Next we create an empty array to store our device details.

    var deviceData = [];

    An entry is added to deviceData if your device is running on a simulator.

    The isSimulator property is used to show the simulator message, only if the recipe is currently running on a platform simulator or emulator.

    if(my.platformHelpers.isSimulator){
      deviceData.push({
        title:'Running on Simulator', child:false
      });
    }
  4. Next the platform, operating system's name, and manufacturer are added to deviceData.

    deviceData.push({
      title:'Platform: ' + 
      (my.platformHelpers.isAndroid ? 'Android' : 'iOS'), 
      child:false
    });
    
    deviceData.push({
      title:'osname: ' +  my.platformHelpers.osname, 
      child:false
    });
    
    deviceData.push({
      title:'manufacturer: ' +  
      my.platformHelpers.manufacturer, child:false
    });
  5. The following statement adds a flag to deviceData indicating if the current device is a tablet form factor:

    deviceData.push({
      title:(my.platformHelpers.isTablet ? 
      'Is a Tablet' : 'Is not a Tablet'), child:false
    });
  6. Next we add the device model and specify if it supports background tasks in the deviceData array.

    deviceData.push({
      title:'Model: ' + my.platformHelpers.deviceModel, 
      child:false
    });
    
    deviceData.push({
      title:'Backgrounding Support: ' + 
      my.platformHelpers.supportsBackground,
      child:false
    });
  7. Screen dimensions are the most commonly used properties. The following snippet adds the height and width of the screen to the deviceData array:

    deviceData.push({
      title:'Height: ' + my.platformHelpers.deviceHeight +
      ' Width: ' + my.platformHelpers.deviceWidth,
      child:false
    });
  8. If your app is currently running on an iOS device, the following snippet adds the device type and specifies if it is retina enabled, to the deviceData array.

    if(my.platformHelpers.isIOS){
      deviceData.push({
        title:'iOS Device Type: ' + 
        (my.platformHelpers.iPad ? 
        (my.platformHelpers.iPadMiniNonRetina ? 
        'iPad Mini' : 'Is an iPad') : 'iPhone'),
        child:false
      });
    
      deviceData.push({
        title:'Is Retina : ' + my.platformHelpers.isRetina,
        child:false
      });				
    }
  9. The PlatformHelper module provides a great deal of data. To best display this, we will be using a Ti.UI.TableView with the deviceData array that we have built in an earlier code snippet.

    var tableView = Ti.UI.createTableView({top:0, 
    data:deviceData});
    
      win.add(tableView);
      win.open();
    })(); 

How it works...

The device platform lookups will frequently be accessed across your app. To avoid performance issues by repeatedly crossing the JavaScript Bridge, we import the values we will use and assign them to properties in our CommonJS PlatformHelpers module.

exports.osname = Ti.Platform.osname;
exports.manufacturer = Ti.Platform.manufacturer;
exports.deviceModel = Ti.Platform.model;
exports.deviceHeight = Ti.Platform.displayCaps.platformHeight;
exports.deviceWidth = Ti.Platform.displayCaps.platformWidth;
exports.densitylevel = Ti.Platform.displayCaps.density;
exports.deviceDPI = Ti.Platform.displayCaps.dpi;

It is often helpful to have a Boolean indicator for the platform with which you are working. The following snippet shows how to create isAndroid and isIOS properties to accomplish this:

exports.isAndroid = exports.osname === 'android';
exports.isIOS = (exports.osname === 'ipad' || 
exports.osname === 'iphone');

Simulator check

Depending on your platform, several features may not work in that platform's simulator or emulator. By using the isSimulator property, detect which environment you are in and branch your code accordingly.

exports.isSimulator = (function(){
  return (Ti.Platform.model.toUpperCase() === 'GOOGLE_SDK' || 
  Ti.Platform.model.toUpperCase()  === 'SIMULATOR' || 
  Ti.Platform.model.toUpperCase()  === 'X86_64')
})();

Background capabilities

Mobile apps are often required to perform tasks in the background. This recipe demonstrates how to perform a version-based capability check to determine if the device your application is running on supports backgrounding/multitasking.

exports.supportsBackground = (function(){

Now perform the following steps:

  1. First check if the user is running on Android. If so, return true as most Android ROMs support background processing by default.

      if(exports.osname === 'android'){
        return true;
      }
  2. Next confirm the recipe is running on an iOS device.

      if(exports.osname === 'iphone'){
  3. The version is checked to ensure that the device is running iOS 4 or greater. This confirms the operating system supports background processing.

        var osVersion = Ti.Platform.version.split(".");
        //If no iOS 4, then false
        if(parseInt(osVersion[0],10) < 4){
          return false;
        } 
  4. Finally the recipe confirms the device version is greater than the iPhone 3GS. This confirms the hardware supports background processing.

        var model = exports.deviceModel.toLoweCase()
        .replace("iphone","").trim();
        var phoneVersion = Ti.Platform.version.split(".");
        if(parseInt(phoneVersion[0],10) < 3){
          return false;
        } 		
      }
      //Assume modern device return true
      return true;
    
    })();

    Note

    Depending on the platform, this feature may be turned off by the user. A secondary capacity check is also recommended.

Detecting tablets

Universal app development is a common requirement on both Android and iOS. The following helper provides a Boolean indicator if your app is running on a tablet form factor:

//Determine if running on a tablet
exports.isTablet = (function() {

Check if the device is either an iPad or an Android device with at least one dimension of 700 pixels or greater.

  var tabletCheck = exports.osname === 'ipad' || 
    (exports.osname === 'android' && 
    (!(Math.min(
    exports.deviceHeight,
    exports.deviceWidth
    ) < 700)));
    return tabletCheck;
})();

Note

For Android, this checks if there is a height or width greater than 700 pixels. You may wish to change this based on your targeted devices. For example, if you are targeting some of the larger screen Android phones, you would need to update the default 700 pixels/points to reflect them having a large screen yet still being considered a phone form factor.

A 4-inch iPhone

With the introduction of the iPhone 5, we need to be aware of two different iPhone screen sizes. The following snippet shows how to create a property indicating if your app is running on an iPhone with this new screen size.

exports.newIPhoneSize = (function(){

First verify the recipe is running on an iPhone device.

  if(exports.osname !== 'iphone'){
    return false;
  }

Next check the size to see if there is any dimension greater than 480 points.

  return (Math.max(
    exports.deviceHeight,
    exports.deviceWidth
  ) > 480);
});

Note

Please note, the preceding newIPhoneSize method will only return true, if the app has been completed to support a 4-inch display. Otherwise, it will return false as your app is running in the letterbox mode.

iPad

Titanium allows you to create universal apps on iOS. Using the following property, you can branch your code to show different functionalities to your iPad users:

exports.iPad = exports.osname === 'ipad';
iPad Mini

The iPad Mini was designed with the same resolution as the first and second generation iPads. Although designed to run iPad apps without modification, the smaller screen size often requires UI adjustments for smaller touch targets. The following code demonstrates how to determine if your app is running on an iPad Mini:

exports.iPadMiniNonRetina= (function() {

Now perform the following steps:

  1. First check if the recipe is running on a nonretina iPad.

      if((exports.osname !== 'ipad')||
      (exports.osname === 'ipad' &&
      exports.densitylevel==='high')){
        return false;
      }
  2. Next verify if the nonretina iPad is not an iPad 1 or iPad 2. If not either of these modules, assume the recipe is running on an iPad Mini.

      var modelToCompare = exports.deviceModel.toLowerCase();
      return !(
        (modelToCompare==="ipad1,1")||
        (modelToCompare==="ipad2,1")||
        (modelToCompare==="ipad2,2")||
        (modelToCompare==="ipad2,3")||	
        (modelToCompare==="ipad2,4")		
      );
    })();

Note

Apple currently does not provide a platform indicator for the iPad Mini. This check uses model numbers and might not be future proof.

 

Global logging using Ti.App.Listener


There is built-in ability of Titanium to fire and listen to application wide events. This powerful feature can be a perfect fit for creating loosely-coupled components. This pattern can provide a decoupled logging approach, which is helpful for specific situations such as analytics, logging, and process monitoring modules.

This recipe details how Ti.App.Listener can be used to create a decoupled re-useable application component. The following screenshot demonstrates this recipe running on both the iPhone and Android platforms:

Getting ready

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

How to do it...

Application-level events allow you to implement a publish/subscribe pattern globally in your app, even across execution contexts. By simply adding a listener such as the following, you can receive messages fired from anywhere in your app:

Ti.App.addEventListener('app:myEvent', myFunctionToHandleThisEvent);

Firing a custom event in Titanium is easy. You simply use the Ti.App.fireEvent and provide the event name and an optional parameter payload. This example shows how to call the app:myEvent listener we defined earlier.

Ti.App.fireEvent('app:myEvent',"My parameter objects");

Tip

It is recommended that you name your events using a descriptive convention. For example, app:myEvent describes that this is an application event and is defined in my app.js file.

Designing global logging using events

The following recipes show how to use application-level custom events to implement logging across the different contexts of our app.

Defining our app.js

In the app.js, we define our application namespace and import the logger and UI CommonJS modules.

//Create our application namespace
var my = {
  ui:{
    mainWindow : require('mainWin').createWindow()
  },
  logger : require('logger')
};

Next in our app.js, the setup method is called on the logger module. This ensures our database logging tables are created.

//Run setup to create our logging db
my.logger.setup();

The application-level listener is then defined in our app.js. This listener will wait for the app:log event to be fired and then call the logger's add method with the payload provided.

//Add a global event to listen for log messages
Ti.App.addEventListener('app:log',function(e){
  //Provide Log Message to CommonJS Logging Component
  my.logger.add(e);
});

//Open our sample window
my.ui.mainWindow.open();
The logging module

The following code snippet demonstrates how to create the basic CommonJS module, logger.js, used in our global listener recipe. This module has two methods; setup, which creates our database objects, and add, which the listener calls to log our messages.

Create a constant with the logging database name.

var LOG_DB_NAME = "my_log_db";

Create the setup module-level method. This is used to create the LOG_HISTORY table, which will later be used to record all log statements.

exports.setup=function(){
  var createSQL = 'CREATE TABLE IF NOT EXISTS LOG_HISTORY '; 
  createSQL +='(LOG_NAME TEXT, LOG_MESSAGE TEXT, ';
  createSQL += 'LOG_DATE DATE)';
  //Install the db if needed
  var db = Ti.Database.open(LOG_DB_NAME);
  //Create our logging table if needed
  db.execute(createSQL);
  //Close the db
  db.close();
};

Create the add module-level method. This method is used to record the information to be added to the log table.

exports.add=function(logInfo){
  var insertSQL ="INSERT INTO LOG_HISTORY ";
  insertSQL +=" (LOG_NAME,LOG_MESSAGE,LOG_DATE)"; 
  insertSQL +=" "VALUES(?,?,?)";
  var db = Ti.Database.open(LOG_DB_NAME);
  //Create our logging table if needed
  db.execute(insertSQL,logInfo.name,logInfo.message, 
  new Date());
  //Close the db
  db.close();
};

Bringing it all together

The following sections show the contents of our mainWin.js module. This module returns a window with a single button that when pressed fires our logging event.

Window and module variable setup

The fetchWindow function returns a simple window demonstrating how to fire an application-level event. To demonstrate, a new message is sent each time, and we add a module level variable named _press_Count, which increments with each click.

Create the module-level variable _press_Count to record the number of times addLogButton has been pressed.

var _press_Count = 0;

The fetchWindow method returns a Ti.UI.Window containing the UI elements for this recipe.

exports.fetchWindow=function(){

A Ti.UI.Window is created for all of our UI elements to be attached.

  var win = Ti.UI.createWindow({
    backgroundColor:'#fff'
  });

The addLogButton Ti.UI.Button is created. This button will be used to trigger the Ti.App.fireEvent used to demonstrate global listeners in this recipe.

  var addLogButton = Ti.UI.createButton({
    title:"Fire Log Event", top:180, 
    left:5,right:5, height:50
  });
Firing the application-level event

On clicking on the addLogButton button, the _press_Count variable is increased by one and a logging object is created. The following code demonstrates how the Ti.App.fireEvent method is called for the app:log event and a JavaScript object containing our logging details is provided. The listener we defined in our app.js will then receive this message and log our results.

  addLogButton.addEventListener('click',function(e){		

The first action taken after the user taps the addLogButton is to increase the _pressCount by one.

    _press_Count ++;

Next the logObject containing information to be submitted for logging is created.

    var logObject = {
      name:'test log',
      message:'example message ' + 
      _press_Count
    };

Finally, the Ti.App.fireEvent is called for the app:log event. The logObject is also provided as a parameter when calling this application global event.

    Ti.App.fireEvent('app:log',logObject);
  });    
  win.add(addLogButton);
    
  //Return window
  return win;
};

Note

App logging is just one of the many examples of how you can use application-level events to decouple your app's components. This pattern will be used several times over the course of this book when decoupled component interaction is required.

There's more...

Since application-level listeners are globally scoped, you need to take caution where and when they are defined. A general rule of thumb is to define application-level listeners in an area of your app that is always available and never reloaded, such as your app.js, CommonJS file which is only loaded once, or another bootstrapping method.

If you must define them within a window or other module that is loaded repeatedly, make sure you remove the listener when the object is no longer needed. To remove the event listener, you need to call the Ti.App.removeEventListener method with the same arguments used on creation. The following snippet shows how to remove the app:myEvent listener created earlier:

Ti.App.removeEventListener('app:myEvent', myFunctionToHandleThisEvent);

Tip

If you do not remove a listener, all associated JavaScript objects will not be eligible for garbage collection, often resulting in a memory leak.

About the Author

  • Benjamin Bahrenburg

    Benjamin Bahrenburg is a developer, blogger, speaker, and consultant. Ben specializes in building enterprise solutions using mobile technologies, geolocation services, and domain-specific languages. Over the last decade, he has provided mobility solutions for numerous Fortune 100 organizations. Ben is passionate about cross-platform development, particularly the use of Titanium mobile for mobile app development. He was an early adopter of the Titanium mobile SDK and has built apps since the earliest previews of Appcelerator's mobile platform. Ben is an active member of the mobile development community and holds a Titanium certification in addition to being part of the Appcelerator Titan evangelist group. A strong advocate and contributor to the Titanium module ecosystem, Ben has published numerous open source modules used in thousands of published apps. An active blogger at bencoding.com, he frequently posts tutorials on mobile development and enterprise coding topics.

    Browse publications by this author
Book Title
Access this book, plus 7,500 other titles for FREE
Access now