Appcelerator's Titanium Mobile is a great tool for cross-platform mobile development. Appcelerator publishes documentation and guides, the Kitchen Sink project gives a view into some best practices, and project templates can get you started quickly. However, overall application architecture is left to the developer, and designing any substantial app can be a daunting task. This article describes my approach for implementing the Model-View-Controller (MVC) architecture within Titanium Mobile Apps. Note: I'm assuming the use of CommonJS for everything.
Project Layout
Here's the basic structure I like to use for projects.
- application - Contains application-specific code. This keeps the folders containing actual code from being separated by those containing images or other general components.
- images - Contains all the image files for the project
- lib - Contains reusable, general purpose libraries. For me, this is usually underscore.js, underscore.string.js, and internal libraries that I extend as I go.
Then, within the application folder, components are organized into the MVC structure.
- controller - Contains the business logic components which access data via model components and present information to the user via the user interface components.
- model - Contains components that provide data access. That means remote data as well as local data, which might include application properties. Ideally, you could change the underlying data source used by your model components without changing any of your other code.
- ui - Contains our application-specific user interface components.
Example
I generally like to setup each controller and data entity with corresponding function calls. My recent work has been on a Customer Relations Management app that involves a fair amount of standard database maintenance functions. So, many of the controllers have functions for list, detail, create, edit, and delete. Their corresponding data entities then have the same or similar functions. The consistency makes maintenance straightforward.
If we were implementing an app that allows users to maintain a list of customers, we might have these components:
Controller
customerController
- Provides access to list, detail, edit, create, and deleteRecord
functions. ("delete
" is a keyword so I usually use deleteRecord
.) I've found that controllers are often non-instantiable libraries, so they are named with an initial lowercase.
Model
customerEntity
- Used by the customerController
to access data. It might implement list, detail, create, and deleteRecord
functions. This example is named for non-instantiable library.
View
CustomerListView
- Used by customerController
to display a list of customers. User interface components are generally instantiable, so they are usually named with an initial uppercase. CustomerDetailView
- Used by customerController
to display details for a single record. (Instantiable) CustomerEditView
- Used by customerController
to edit existing records and to create new records. (Instantiable)
Here's what it would look like within the project. Note that the user interface elements are further organized into folders named for their controller. I've also added a model.js library which would contain application-specific functions common to multiple data entities.
If you want to apply this structure to everything, you could add an application controller which might handle your bootstrap logic and open your application window. Perhaps you'd have an application entity that would manage access to configuration settings.
Now as we add more and more functionality, the structure stays very organized.
Why Bother?
If you don't currently separate business logic from user interface code, you might think, "That's all very nice, but why bother doing this? I'm still writing the same code. It's just in different places now. If I want to add a field to a screen, I'll have to edit two or three files instead of one!" Well, consider this: What if during the design phase, you notice that the views which list records are pretty much the same. You might write a single component that you instantiate with the data and callback functions that it needs to function. Part of a customer controller that uses this generic list view might look something like this:
var ListView = require('/lib/ui/ListView'),
customerEntity = require('/application/model/customerEntity');
.
.
.
var create = function(args){
};
var detail = function(args){
};
var list = function(args){
{fieldName: 'company_name', type:'text', ....},
{fieldName: 'address', type:'text', ....},
{fieldName: 'referral_source', type:'text', ....},
{fieldName: 'initial_contact', type:'date', ....}
],
listData = customerEntity.list(),
customerListView = new ListView({
fieldDefs : fieldDefinitions ,
data : listData
onRowClick : detail
onNewClick : create
}); .
.
.
};
At this point, you've replaced a bunch of potentially mind-numbing UI code with a reusable component that you can add to your personal library, you've guaranteed consistency across all the listing type views, and now you have only a single object to maintain for all of your lists.
What if you can do the same thing for the detail and edit views? Now, to add a field to your detail view, you just need to adjust your data entity to include the field, and define its attributes in the fieldDefinition
object for the generic detail view. This type of code reuse is one of the benefits of separating business logic from user interface.
Additionally, from a maintenance perspective, it's nice to have the components separated. You can address changes to business logic without wading through lines of display logic. Having data access encapsulated provides you some extra flexibility that you may need as your app matures. You may need to change one aspect of your app that has always used live remote data to something that can now function offline with a local database. Under this type of architecture, you should be able to make that change without ever touching your controller or the associated user interface.
Wrap-Up
OK, I started with a nice overview of how to structure an app according to MVC, and provided some nice conceptual examples. Then I had to go and include a potentially confusing code snippet! If the code doesn't make sense to you, just set it aside for now. Take another look when you need to do something similar. I think it's a powerful approach which I've implemented, but it can make the head spin if you haven't done something like this before. Perhaps a topic for another day!