Saturday, May 21, 2011

Titanium MVC Pattern

Are you looking to simplify your Titanium programming model? Leveraging the MVC pattern will help produce cleaner code and promote reusability. Applying the MVC pattern within Titanium is relatively easy and it provides all the advantages we expect from MVC. Here is a quick example of the view and controller components:


View

The view is responsible for creating the window and adding the necessary UI components. Your views may optionally manage styling properties (color, font, and positioning). Ideally, the better practice is to extract all styling properties into external JSS files but I found this feature to be too quirky in its initial release.


/**
* View Template
* Usage: APP.ui.LoginView.createWindow();
*/

APP.ui.LoginView = (function(){

/* private methods to build UI specific components */
function buildWindow(){
return Ti.UI.createWindow({
title: 'Title',
barColor: '#225377',
backgroundColor: '#d4d2d3'
});
}

function buildUsernameTextField(win){
win.usernameTextField = Ti.UI.createTextField({
autocorrect: false,
hintText: 'Username',
left: 10,
width: 285,
suppressReturn:false,
autocapitalization: Ti.UI.TEXT_AUTOCAPITALIZATION_NONE,
clearButtonMode: Ti.UI.INPUT_BUTTONMODE_ONFOCUS,
borderStyle: Ti.UI.INPUT_BORDERSTYLE_NONE,
returnKeyType: Ti.UI.RETURNKEY_NEXT
});
}

/* Public API only exposes the createWindow method. */
return {
createWindow: function(){
var win = buildWindow();
buildUsernameTextField(win);
//buildComponentX(win);
return win;
}
};
})();


Controller

The controller is responsible for managing events (button taps) and navigation. Events handlers communicate with server-side services when necessary.

/**
* Controller Template
* Usage: APP.ui.LoginController.init();
*/

APP.ui.LoginController = (function(){

/* Private helper methods */
function setEventListeners(win){
win.usernameTextField.addEventListener('return', function(){
win.passwordTextField.focus();
});

win.passwordTextField.addEventListener('return', function(){
handleLoginEvent(win);
});
}

/* Public API only exposes init method. */
return {
init: function(){
var win = APP.ui.LoginView.createWindow();
setEventListeners(win);
win.open();
}
};
})();


Model

The model is typically a JSON payload retrieved from a Restful service. For example, the handleLoginEvent will return user profile information after a successful login. We can save this object locally and leverage it on subsequent screens.


Advantages:
  • Small objects and methods.
  • Improved organization and readability
  • Promotes reusability of views. Also, the decoupled views allow you to swap in native-specific UIs when necessary all while reusing a single controller.
  • Simpler maintenance.
  • Allows for simpler unit testing with responsibilities split into separate objects.


I expect many developers may fall in the trap of modeling their JavaScript according to the KitchenSink examples. While those examples are useful, their programming practices should not be followed for a production ready app. For example, their examples do not leverage closure to eliminate global scope, they do not use a namespace convention for improved organization, and all MVC responsibilities were bundled within a single object.

9 comments :

  1. This is a very helpful post - do you have a working example for Titanium?

    In the "buildUsernameTextField" function above you have assigned the text field element to "win.usernameTextField" but it hasn't been added to the window using "win.add(...)".

    How would you add it to the window but still ensure that you can reference the text field element in the controller for the purpose of adding event listeners?

    ReplyDelete
  2. Hi James,

    The controller can access the field level components as properties exposed on the window object. Refer to the first line in the buildUsernameTextField function (win.usernameTextField = Ti.UI.createTextField). This assignment will expose the field on the window object.

    To handle the win.add(...) I added my text fields to a table view. I wanted an iOS styled table grid with rounded corners. After adding the text fields to the table view I then called win.add(myTableview); This step depends on how you want to group your components. I'll also send you the complete code. The complete view also includes a navigation group and a remember me switch.

    Regards,
    Brad

    ReplyDelete
  3. Hi Brad,
    to me this post is also very helpful.
    Could you publish the complete code somewhere? For me it would help to completely understand how Controller, Model and View work together.
    Thanks a lot!
    Rob

    ReplyDelete
  4. For those interested in the complete code example I just published it on GitHub: https://github.com/BradBroulik/titanium-mvc

    ReplyDelete
  5. Great work Brad, this is a great starting point for a MVC-backed Titanium project.

    Thanks a lot for putting everything up!

    ReplyDelete
  6. Hi Brad,

    I started a project on base of your code example.

    I have one component that also incorporates event listeners like in your code. But it seems that I can't access the event object, e.g. e.rowData.title of a tableview click.

    Here is part of my code: http://pastebin.com/ZZw9ZD9V
    I refer to the lines 20-30. This code never will execute when user clicks the table view. Lines 11-16 on the other hand will execute. That shows me that the complete construct "per se" works.

    I suppose the problem is that the event object _e is not available in the scope of the function setTableviewEventListeners(). But I don't know how to solve this. Maybe you have an idea?

    Thanks again and best regards
    Rob

    ReplyDelete
  7. Oh silly me...! :-( Got the mistake.
    The parameter allowsSelection=false avoided the event to be thrown. It has nothing to do with the scope...
    Sorry for polluting your blog...
    Rob

    ReplyDelete
  8. Hi Brad,

    I really liked your example it helped me to understand this approach considering that I am from core java background. I was wondering if you can give me some kind of example/tips for writing a tab based application. Also, I need to have a common class or something like that keeps all information about the application specific info.

    ReplyDelete
  9. Can we manage backward compatibility in control view controller while developing iPhone app in Titanium SDK (similar to Shopstyle iPhone App)?

    [When a main tab bar item is selected, that selected item should open in Model view controller & shall manage further navigation along with backward navigation.]

    Please let us know ASAP as out team is under developing such feature.

    ReplyDelete