Mobile web application framework match-up, Part 2: Explore Cappuccino for mobile web app development

Web applications have been supplanting desktop applications for many years despite the fact that they are generally of lower quality than their desktop counterparts. Part of the reason for this discrepancy is the comparatively greater capabilities of desktop applications for running inside a browser. However, the feature and performance gap has shrunk rapidly with the recent advances in modern browsers and their implementation of the HTML5 specification. The other major reason has been that web developers have had to make do with much lower-level APIs than desktop developers. Cappuccino changes all of that by bringing the renowned Cocoa framework to web development, which makes it an especially attractive choice for mobile web developers. Decide for yourself if Cappuccino is a good fit for your next great mobile web application.

Michael Galpin, Software Architect, eBay

Michael Galpin's photoMichael Galpin is an architect at eBay and a frequent contributor to developerWorks. He has spoken at various technical conferences, including JavaOne, EclipseCon, and AjaxWorld. To get a preview of what his next project, follow @michaelg on Twitter.



23 November 2010

Also available in Chinese Japanese

About this series

Mobile development has taken off, so many developers are choosing to go the mobile web route instead of writing the same application repeatedly for each different mobile platform. However, one of the things that you give up by "going web" is the application frameworks that make life easier for developers of native mobile applications. As a result, several web application frameworks are emerging. In this four-part series, we will look at four of these frameworks: SproutCore, Cappuccino, jQTouch, and Sencha Touch. We will compare the features of these frameworks and evaluate the pros and cons of using them to build a mobile web application.


Prerequisites

In this article, we will explore Cappuccino and a typical application created using it. Cappuccino introduces its own programming language, Objective-J, that is very similar to Objective-C. Therefore, prior knowledge of Objective-C (or the languages it that draws from, C and Smalltalk) will make learning Objective-J easier. There are no server-side components needed for Cappuccino since it is purely client-side. You only need a basic web server for your Cappuccino applications. See Resources for links to these tools.


An overview of Cappuccino: Hello, Objective-J

If you have read Part 1 of this series, then some of this information might sound a little familiar. In the previous article, we talked about how the SproutCore framework "brings a programming model inspired by Cocoa." This statement does not describe Cappuccino as well, since Cappuccino is not a framework inspired by Cocoa but instead is Cocoa on the web. This might seem absurd whether or not you are familiar with Cocoa. After all, Cocoa is a desktop application framework written entirely in Objective-C. Cappuccino, on the other hand, is a pure client-side web application framework, and Objective-C cannot be used in a browser. This is where Objective-J comes into the picture.

Objective-J is a new programming language inspired by Objective-C. If you are familiar with Objective-C, then you will recall that it is actually a superset of C. Similarly, Objective-J is a superset of JavaScript. It brings much of the syntax that Objective-C adds on top of C, but instead to JavaScript. Objective-C programmers sometimes have to drop down into C, but usually only to do low-level tasks or to use a C library. The same is true for Objective-J programmers. By bringing the Objective-C syntax to JavaScript and thus to the browser, Cappuccino makes it possible to port much of the Cocoa framework to the web. To get a better feel for Cappuccino and the advantages it offers, take a look at a Cappuccino application.

Cappuccino applications

To make an apples-to-apples comparison, we will take the application we developed in Part 1 of this series and re-write it using Cappuccino. Though we will not try to make it look just like the original version, we will try to reproduce all of that application's functionality. Recall that the application is a company's internal directory of employees and their contact information. For our SproutCore implementation, we started by creating a data model, since this is a key part for an MVC framework like SproutCore—and Cocoa. So let's begin our Cappuccino implementation by creating a similar data model. Listing 1 shows the employee data model in Objective-J.

Listing 1. Objective-J employee data model
@implementation Employee: CPObject
{
    CPString firstName @accessors;
    CPString lastName @accessors;
    CPString phone @accessors;
    CPString email @accessors;
}

- (CPString)fullName
{
    return [[CPString alloc] initWithFormat:@"%@ %@", firstName, lastName];
}

@end

The first thing you might notice about the code in Listing 1 is that it starts with the @implementation keyword. In Objective-C, each class has two files. The first is the header (.h) file that declares the interface of the class. The second is the implementation (.m) file, where all of the methods of the interface are implemented. The @implementation is used to declare which class is being implemented. Fortunately, Objective-J doesn't use header files, only an implementation file. After the @implementation keyword, we see the name of the class (Employee) and its superclass. In Objective-C, the base object is NSObject. In Cappuccino, it is CPObject. In other words, every class is a CPObject. Notice that most core classes in Cappuccino start with the CP prefix, which stands for Cappuccino and follows the tradition of Cocoa.

Notice that in Cocoa, core classes usually have the NS prefix; this stands for NextStep, the operating system that originally introduced Objective-C.

Next in Listing 1 come the fields/properties of the class. Note that the type of each variable is declared first. In this case, each is of type CPString. The name of the variable follows. Finally, we see the @accessors annotation, which is a macro that creates getter/setter methods for the field that precedes it. Listing 2 shows the equivalent code that will be generated for the firstName field.

Listing 2. Accessor code
- (CPString)firstName
{
    return firstName;
}

- (void)setFirstName:(CPString)aFirstName
{
    firstName = aFirstName;
}

As you can see, that single @accessors annotation replaces eight lines of code. However, keep in mind that the code in Listing 2 is exactly what will be generated for each of the fields with the @accessors annotation. So to get the value of firstName, invoke the first method (firstName) shown in Listing 2. Similarly to set the value, invoke the second method (setFirstName:). This does not provide "dot access" (employee.firstName or employee.firstName = someOtherName, for example) as happens with Objective-C's synthesized properties and perhaps some JavaScript properties.

Return to Listing 1. After the fields and their accessors are declared, the methods of the Employee class come next. In this case, we only have one method, fullName. This method simply returns the firstName and lastName fields, concatenated with a space in between. The implementation shows several key parts of Objective-J. To create a new object (in this case, the string that will be returned), first alloc the object and then initialize it. The alloc method is inherited from CPObject, but the initWithFormat: is unique to CPString. In general, different classes have various ways to initialize new instances. To invoke these methods, use the [object method] syntax. Method invocation can be thought of as message passing to the object—that is, you are telling it to alloc, and telling the result to initWithFormat:. This is borrowed directly from Objective-C.

The last thing to notice about the fullName method in Listing 1 is that it is preceded by a minus sign (-). This denotes that the method is an instance method. A plus sign (+) denotes a class or static method. These are not as common as instance methods, but you will often see class methods that are factory methods for creating new instances of the class; this saves you from having to alloc and initialize. Having gotten familiar with Cappuccino by examining the data model for our application, now examine the view and event management code.

Creating views and working with delegates

When it comes to developing a rich and interactive user interface, Cocoa has always excelled. This is partly because of the flexible delegation model that it provides. Another contributing factor to Cocoa's success is the toolset that it provides. For years, Mac and iOS developers have enjoyed Interface Builder, a visual designer for Cocoa. This program not only enables drag-and-drop creation of user interfaces, but also allows developers to visually "wire" the UI controls to methods on the underlying application controllers. The results are serialized ("freeze dried") UI objects that are ready to be used in an application. These files are serialized as .xib files, which is a type of XML document. The combination of Interface Builder and .xib files is a powerful one. Cappuccino uses a similar feature, called .cib files, for storing the UI objects. A tool called Atlas can be used in much the same way that Interface Builder is for Cocoa applications.

Alternatively, you can create your user interfaces programmatically from within your Cappuccino application. For simpler applications, this is often the easiest thing to do, as all of your code is in one place. This slightly contradicts the MVC paradigm, since you typically create your view from within your controller, but it's a reasonable simplification for many cases. Listing 3 shows the controller for your application.

Listing 3. Declaring the application controller
@import <Foundation/CPObject.j>
@import <Foundation/CPDictionary.j>
@import <Foundation/CPValue.j>
@import <Foundation/CPURLConnection.j>
@import <Foundation/CPURLRequest.j>
@import <AppKit/CPTableView.j>
@import <AppKit/CPScrollView.j>
@import <AppKit/CPTableColumn.j>
@import "Employee.j"

@implementation AppController : CPObject
{
    CPTableView tableView;
    CPDictionary data;
}

The code in Listing 3 is the basic declaration of your controller. The first thing to notice is the use of the @import directives. Just like with Objective-C, you must declare your dependencies for your application to work in Objective-J. In fact, these are all separate files that the Cappuccino runtime will load asynchronously. This not only helps with managing code dependencies, but also reduces the initial JavaScript download of Cappuccino, making your application faster. Notice the instance variables (or ivars, as Cocoa and Cappuccino usually refer to them) for your controller in Listing 3. In this case, a CPTableView shows the data in the CPDictionary that will hold all of the data loaded from the server. When the application is ready to start executing, the method in Listing 4 will be invoked.

Listing 4. Application initialization code
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
    // initialize data
    data = [CPDictionary dictionary];
    
    // create the frame
    var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() 
        styleMask:CPBorderlessBridgeWindowMask];
    var contentView = [theWindow contentView];
    [theWindow setTitle:@"Employee Directory"];
    [theWindow orderFront:self];

    // create the table
    tableView = [[CPTableView alloc] initWithFrame:[contentView bounds]];
    [tableView setAutoresizingMask:CPViewWidthSizable];
    
    // create the columns
    var nameColumn = [[CPTableColumn alloc] initWithIdentifier:@"name"];
    [[nameColumn headerView] setStringValue:@"Name"];
    [[nameColumn headerView] sizeToFit];
    [tableView addTableColumn:nameColumn];
    var phoneColumn = [[CPTableColumn alloc] initWithIdentifier:@"phone"];
    [[phoneColumn headerView] setStringValue:@"Phone"];
    [[phoneColumn headerView] sizeToFit];
    [tableView addTableColumn:phoneColumn];
    var emailColumn = [[CPTableColumn alloc] initWithIdentifier:@"email"];
    [[emailColumn headerView] setStringValue:@"Email"];
    [[emailColumn headerView] sizeToFit];
    [tableView addTableColumn:emailColumn];

    // create scrollable container for table
    var scrollView = [[CPScrollView alloc] initWithFrame:[contentView bounds]];
    [scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
    [scrollView setDocumentView:tableView];
    [scrollView setAutohideScrollers:YES];
    
    [contentView addSubView:scrollView];
    
    [tableView setDelegate:self];
    [tableView setDataSource:self];
    
    // load data from server
    var connection = [CPURLConnection connectionWithRequest:
        [CPURLRequest requestWithURL:"/app/employees.json"] 
        delegate:self]; 
}

Listing 4 has a lot of code, but it is actually quite straightforward. To begin, focus on the UI code. First, create the main window/frame for the application; next, a data table using the CPTableView class, the columns of the table, and each column to the table. Finally, create a CPScrollView to put the table in. This will make the table scroll if there are too many rows to display on the screen.

Listing 4 shows how easy it is to create UI objects using Cappuccino. Notice that we also set the table's delegate and data source to self. That means that the controller will implement delegate and data source methods.

First, examine the last line, where data is loaded from the server. We use CPURLConnection and CPURLRequest to request this data. This is done behind the scenes using an XMLHttpRequest in classic Ajax style. However, instead of specifying a callback method, specify a delegate for the connection, once you have set this to self. Thus, a method on the controller will be invoked when the server responds to the request from the application. The CPURLConnection's delegate class defines the name of the method to be invoked. In this case, the delegate has four methods: connection:didFailWithError:, connection:didReceiveResponse:, connection:didReceiveData:, and connectionDidFinishLoading:. Since the delegate is the controller class, you can implement any of those four methods—and they are all optional. In this case, ignore the error cases and, in Listing 5, only implement the connection:didReceiveData: method.

Listing 5. Connection delegate method
// connection handler
- (void)connection:(CPURLConnection) connection didReceiveData:(CPString)sData
{
    var result = CPJSObjectCreateWithJSON(sData);
    data = [CPDictionary dictionaryWithJSObject:result];
    connection = nil;
    [tableView reloadData];
}

The method in Listing 5 will be invoked when a successful response is returned from the server. The server's response will be the sData string passed to the method. Use another Cappuccino function, CPJSObjectCreateWithJSON, to parse the data into a JavaScript object. Then create a CPDictionary using that JavaScript object. Finally, reload the table to show the new data from the server. This is a great example of how Cappuccino isn't just a port of Cocoa, but instead Cocoa for the web, with many features added to make web development easier. Return briefly to Listing 4, where you defined the data source for the table as the controller. This means that the controller has some data source methods to implement; Listing 6 shows those.

Listing 6. Table data source methods
// data source methods
- (int)numberOfRowsInTableView:(CPTableView)tView
{
    return [data count];
}

- (id)tableView:(CPTableView)tView objectValueForTableColumn:
(CPTableColumn)tableColumn row:(int)row
{
    return [data[row] objectForKey:[tableColumn identifier]];
}

You can see in Listing 6 that two methods are needed to fulfill the data source contract. The first one is numberOfRowsInTableView:. The number of rows is simply the size of the CPDictionary. The more interesting method is the tableView:objectValueForTableColumn:row: method, which is called for each cell in the table. Simply pull this out of the CPDictionary created from the server response. This is a good example of the nested style of method invocation that can make your Objective-J code very concise. Having seen the data source methods implemented by the controller, take a look at the delegate methods that it implements in Listing 7.

Listing 7. Table delegate methods
// event handlers
- (void)tableView:(CPTableView)tView sortDescriptorsDidChange:(CPArray)oldDescriptors
{
    [data sortUsingDescriptors:[tView sortDescriptors]];
    [tView reloadData];
}

Several different methods are defined on the CPTableView's delegate. Each of these corresponds to an event that can be raised by the table. In this case, we are only interested in one event—when somebody clicks on the header to re-sort the table—so you only have to implement the corresponding method, tableView:sortDescriptorsDidChange:. You can see the convenience of Cocoa at work here: you simply get its new sort descriptors from the table, use them to re-sort the underlying CPDictionary, and then reload the table. This will cause the tableView:objectValueForTableColumn:row: method from Listing 6 to be called again for each cell in the table, resulting in the re-sorted data being displayed. Having glimpsed several of the powerful features of Cappuccino, consider its use for mobile web applications.


Cappuccino and mobile

Though the simple application that we have developed demonstrates many of the core capabilities of Cappuccino, it says nothing about Cappuccino and the mobile web. To better understand how well suited Cappuccino might be for mobile web applications, look at the framework that inspired Cappuccino—Cocoa. If you go back to Listing 3 and look at its import statements, you will notice that each of the UI elements (CPTableView, CPScrollView, and CPTableColumn) is from the AppKit module. In the world of Cocoa development, AppKit is the UI module for desktop applications. However, if you have only done iOS development, then you might not be familiar with AppKit because it is not part of the iOS SDK. Instead, it is replaced by UIKit. These are UI elements that are specifically designed for the iPhone, not for desktop applications. Cappuccino includes many classes from AppKit, but none of the classes from UIKit. In other words, it does not include any UI elements that were specifically designed for mobile devices, only elements designed for desktop applications. As a result, you might think twice about using any of Cappuccino's AppKit classes for a mobile web application. However, you can still create your own custom UI elements using either low-level APIs or Cappuccino's CoreGraphics library.


Conclusion

This article explored many of the features of the Cappuccino framework. Cappuccino has a lot to offer from a web application perspective. It embraces a programmatic approach to creating all of your UI on the client, and only getting data from the server. This architecture is optimal for mobile web applications. It also provides you with an elegant application model, making heavy use of the delegate pattern to eliminate much of the boilerplate code from your application. On the other hand, to make the most of Cappuccino, you need to learn Objective-J, which might not be a trivial matter for web developers. More significantly, its UI elements are not designed for mobile use. That doesn't necessarily mean that they won't work, but it does mean that they might not take advantage of things like touch events. The ease of development that it provides—especially when combined with its Atlas tool—might still be a good tradeoff, depending on the complexity of your application.


Download

DescriptionNameSize
Article source codecappdir.zip2.1MB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=588499
ArticleTitle=Mobile web application framework match-up, Part 2: Explore Cappuccino for mobile web app development
publish-date=11232010