OR: An example of a master > detail app.
This is a Titanium Alloy sample app that creates a RSS reader. The app lets you pull a live RSS feed from the internet, list the articles and then drill down to the article itself.
As you'll see the code is heavily documented to tell you exactly what is going on.
Let's run through some of the main topics covered.
For Android, the app uses a custom Android Material Theme. Material made it much easier to create custom Android themes and all you have to do is override some of the default colors in platform/android/res/values/custom_theme.xml and select the style name in tiapp.xml under android/manifest/application@android:theme.
- Guide: Android Themes
We've made it very easy to change the URL for the RSS feed without touching the code. It's set in app/config.json under global/url and read as Alloy.CFG.url
in app/controllers/master.js. As you can read in the guide, we could even set a different value for this propery based on the platform and environment the app runs on.
For iOS we need to set the colors for our toolbars on each Window. Fortunately we can use app/styles/app.tss to set styles that apply to all views. This is also a great place to reset/normalize, like we do for Labels, which are by default white
on Windows and some grey
on Android.
- Guide: Alloy Styles and Themes
As the screenshots show, we use platform and even device specific UI elements to provide the best user experience.
We could use Titanium and Alloy's support for platform-specific resources and create an Android-specific view in app/views/android/index.xml
and an iOS-specific view in app/views/iphone/index.xml
.
In this case we chose to use Alloy's support for conditional code to keep them all in one view. We would have needed this for an iPhone and iPad specific view anyway and this way you can see how we handle all platforms and form factors in one glance.
You will see we use conditional code in the other two views as well to use platform-specific UI elements like Android ActionBar menu items and a RefreshControl for iOS.
To keep our code DRY we create a seperate master controller for the list. As you can see, we require this controller in all four different navigation patterns in app/views/index.xml.
- Guide: Require Element
The app uses the following navigation patterns:
NavigationWindow
for iPhone and the similarNavigationGroup
for MobileWeb. The classic example of this pattern is the Settings App on iOS. It stacks multiple windows, showing the title of the current window in a navigation bar that also has a Back button to pop back to the previous one. Ideal for hierarchies of two or more levels like our master > detail.SplitWindow
for iPad is a pattern you will know from the Mail App on iOS. It displays two windows, one narrow and the other wide. Ideal for a single level master > detail app on bigger screens.- And then finally, for Android we simply open windows on top of each other without managing much about them. And for Android this works just great because it has hardware buttons for navigation back as well as an Action Bar that we can set to display a back button.
In app/views/master.xml you can see we use a ListView for iOS and Android and a TableView for MobileWeb, which does support ListViews.
ListView is the more efficient and faster successor of TableView, but there are some specific use cases for which we keep the TableView around for all platforms. The main difference is that ListViews use templates and data while TableViews are composed of regular views.
ListViews can be more difficult to implement in Titanium, but as you can see Alloy hides most of that complexity from you. So if you can, always use ListViews instead of TableViews.
- Guides:
Perhaps the most complex part of our app is the use of Alloy Collections, Models and data-binding to fetch and display the RSS feed. It is really just seen as complex because Alloy does most of the work, which makes it feel like magic.
Embrace the magic and unleash the power of BackBone.js in Alloy:
Models can be seen as the rows of a table and their definition configures the columns and the connection to where the rows are read from and synced to. The columns are not required for all types of sync adapters and as you can see our app/models/feed.js only has the configuration for our custom RSS sync adapter.
Alloy comes with a few ready-made sync adapters of which the one for SQLite is probably the most interesting one. You can add custom sync adapters by dropping a CommonJS module that follows the expected signature in app/lib/alloy/sync
and use the filename in the model definition. A popular community adapter is restapi by Mads Möller.
For this app, we created a custom rss adapter that reads a URL or path to a local file. For MobileWeb we use a local file because our RSS feed currently doesn't send a Access-Control-Allow-Origin
header. When it is done, it calls the opts.success
callback with the fetched data and BackBone will create and collect the models. We also fire a fetch
event so that our data binding is triggered.
Collections can be seen as tables. They keep models and fire events if there are any changes. This is exactly what we need for data binding.
Data binding simply comes down to listening to changes on an object and updating the UI accordingly. To do this we add a dataCollection
tag to a view element and Alloy will use that element's children to populate the view for each model in the collection.
In the master controller we can now call the collection's fetch()
method to have it populate itself using the rss sync adapter which in turn will trigger the data binding to display them in the list. As you can see we also have the option to transform the data by setting a dataTransform
function in the view.
- Guide: Alloy Models, also on Collections, Data Binding and Sync Adapters
The last piece of our puzzle is opening an article from our list. The master view sets a select
callback to be called when the user taps on a row. You can also see that it binds the identifying guid
field of the models to a special itemId
attribute of our rows.
The master controller defines the callback, receives the id of the model that comes with the event, looks up the model and then fires an event on the controller itself. We haven't talked too much about it, but every controller extends the BackBone.Events
prototype, which allows it to dispatch events.
The reason we don't open the detail window directly from masters is that it depends on the navigation pattern how exactly we should. It's a best practice to keep controllers unaware of their context so we can re-use them in different contexts. So in the index view we bind an onSelect
listener to the select
event of the master controller. In the index controller you will see that this again uses Conditional Code to determine how exactly to open the detail window, passing the model to the detail controller.
- Guides:
- Event Handling in Alloy views
- Alloy Controllers
The detail controller then receives the model and uses it to set the window title and WebView URL. The WebView can be seen as a chromeless browser, embedded in the app. Though one of Titanium's unique features is the use of native UI components, you can easily intergrate existing local or remote HTML and even communicate with it.
- Guide: Integrating Web Content