Appcelerator Studio contains a number of samples for new developers to use as learning tools. The Corporate Directory app showcases cross-platform development, the benefits of Titanium Stylesheets and Alloy Backbone / Models.
This exercise is a walkthrough of the Corporate Directory app to demonstrate the use of Titanium and Alloy for developing mobile apps.
Cross Platform Navigation
This is a simple app with only two major screens. However, we included a cross-platform navigation approach that could be leveraged no matter how many screens are present in the app. This provides a basis for creating great apps for iOS, Android and Windows, while still using native platform design paradigms.
In the index.js
, you will find a global object, Alloy.Globals.Navigator
. This object can be accessed from anywhere in the app, and allows developers to quickly open new windows onto the existing navigation stack. iOS does handle the window stack a bit differently, as does MobileWeb, so we have to make some minor modifications to leverage the platform-specific pieces in the index.xml
.
<!-- app/views/index.xml -->
<Alloy>
<Require id="index" src="directory" platform="android,windows" />
<!-- iOS Window -->
<NavigationWindow id="nav" platform="ios" class="container">
<Require src="directory" />
</NavigationWindow>
<!-- MobileWeb -->
<Window platform="mobileweb">
<NavigationGroup id="nav" class="container">
<Require src="directory" />
</NavigationGroup>
</Window>
</Alloy>
In the view layout, the Directory View is included immediately for Android and Windows. This is because of the standard nature of the Window stack for those platforms. All that is needed is to open up a new Window/Activity and the underlying operating system understands how to manage the navigation through back buttons etc.
iOS and MobileWeb behave a bit differently using the NavigationWindow or NavigationGroup respectively. These are created independently and are both given the same ID for reference in the global navigation object. This is not a problem as the platform
property is used denoting which element should be rendered depending on the platform the app is currently being compiled for.
For more information on the
platform
property, see the Appcelerator Alloy XML Markup Documentation.
/**
* Global Navigation Handler
*/Alloy.Globals.Navigator = {
/**
* Handle to the Navigation Controller
*/navGroup: $.nav,
open: function(controller, payload){
var win = Alloy.createController(controller, payload || {}).getView();
if(OS_IOS){
$.nav.openWindow(win);
}
else if(OS_MOBILEWEB){
$.nav.open(win);
}
else {
// added this property to the payload to know if the window is a child
if (payload.displayHomeAsUp){
win.addEventListener('open',function(evt){
var activity=win.activity;
activity.actionBar.displayHomeAsUp=payload.displayHomeAsUp;
activity.actionBar.onHomeIconItemSelected=function(){
evt.source.close();
};
});
}
win.open();
}
}
};
The JavaScript for handling the cross-platform navigation is also broken into platform-specific code, but abstracts the actual functions to one open function, making this global object very easy to use. For all platforms the window, win
is created using the Alloy.createController
method and then opened based on the platform-specific command.
This single function makes it easy to open a new window anywhere in the app, with the look and feel and behavior expected for the underlying platform.
Splash Screen
Appcelerator Titanium allows developers to create custom splash screens to enhance the overall
experience for the end user. These are created by providing images with specific names and sizes located in the platform-specific folders found under app/assets
folder.
The corporate directory app uses a simple custom splash screen with the Appcelerator Logo prominently featured in the center.
Note: Apple prefers developers to take the approach of minimizing the perceived startup time
of your app by using simple splash screens. While going against this recommendation does not seem to impact the ability to submit to the app store, be aware of this best practice.
Screen 1 – Loading Animation (loader.xml/tss/js)
This sample app includes a loading animation of a rocket blasting off. While all data is actually shipped with the sample and there wasn’t a need to actually load data here, it was included as an example of the animation capabilities of Titanium. Titanium supports several types of animations techniques that are cross-platform using Ti.UI.ImageView
and Ti.UI.Animation
objects.
This particular example leverages the included Ti.UI.ImageView
image array animation and the Ti.UI.Animation
object along with the .animate
method inherited from Ti.UI.View
objects. The ImageView animation is very similar to sprite animation, except that you don’t have direct control of what image is showing. To leverage this style of animation, you pass an array of images to the images
property of the Ti.UI.ImageView
. The Ti.UI.ImageView
has methods called start()
and stop()
that control the animation.
Adding the ImageViews is easily done through the XML view file.
<!-- app/views/loader.xml --> <Alloy> <Window id="rocket" class="container"> <View id="overlay" /> <ImageView id="rocketSmoke" /> <ImageView id="rocketFlight" /> </Window> </Alloy>
Adding the images array to the ImageView can be done in either the javascript or the Titanium Style Sheet (TSS). In this app, the TSS is used to populate the Ti.UI.ImageView.images
array.
// app/styles/loader.tss
“#rocketFlight”: {
width: 110,
height: 130,
opacity: 0,
duration: 0.02,
images: [
“/images/RocketFlight/RocketFlight01.png”,
“/images/RocketFlight/RocketFlight02.png”,
“/images/RocketFlight/RocketFlight03.png”,
“/images/RocketFlight/RocketFlight04.png”,
“/images/RocketFlight/RocketFlight05.png”,
“/images/RocketFlight/RocketFlight06.png”,
“/images/RocketFlight/RocketFlight07.png”,
“/images/RocketFlight/RocketFlight08.png”,
“/images/RocketFlight/RocketFlight09.png”,
“/images/RocketFlight/RocketFlight10.png”,
“/images/RocketFlight/RocketFlight11.png”,
“/images/RocketFlight/RocketFlight12.png”,
“/images/RocketFlight/RocketFlight13.png”,
“/images/RocketFlight/RocketFlight14.png”,
“/images/RocketFlight/RocketFlight15.png”,
“/images/RocketFlight/RocketFlight16.png”,
“/images/RocketFlight/RocketFlight17.png”,
“/images/RocketFlight/RocketFlight18.png”,
“/images/RocketFlight/RocketFlight19.png”,
“/images/RocketFlight/RocketFlight20.png”,
“/images/RocketFlight/RocketFlight21.png”,
“/images/RocketFlight/RocketFlight22.png”,
“/images/RocketFlight/RocketFlight23.png”,
“/images/RocketFlight/RocketFlight24.png”,
“/images/RocketFlight/RocketFlight25.png”
]
}
The animation is managed by the controller file. Below is an example:
// app/controllers/loader.js
$.rocketSmoke.start();
};
Along with the sprite style animation that is built into the ImageView, there are several animation transformations used for changing the opacity and moving the rocket. The code below demonstrates how easy it is to move objects on the screen.
// app/controllers/loader.js
$.rocketFlight.animate({
top: -130,
duration: 750,
curve: Ti.UI.ANIMATIONCURVEEASE_IN
});
In the above example, the Ti.UI.Animation
object isn’t explicitly created, however it is created based on the dictionary object passed to the .animate
method. This could also have been written like this:
// app/controllers/loader.js
var rocketFlightAnimation = Ti.UI.createAnimation({
top: -130,
duration: 750,
curve: Ti.UI.ANIMATIONCURVEEASE_IN
});
$.rocketFlight.animate(rocketFlightAnimation);
Creating the animation object is preferred when you are looking to reuse the animation.
Note. Apps that are heavy on animation patterns will often times leverage a commonJS module for creating standard animations
Screen 2 – Directory list (directory.xml/tss/js)
The main screen of the Corporate Directory app is single window with a Ti.UI.ListView
for rendering the sample data found in the app/lib/userData/data.json
file. The list is sorted into list sections and ordered alphabetically.
The directory list is also searchable. Typing in the at least 3 digits will filter the list accordingly.
Try it out! Type in ‘por’ into the list to see how it filters down.
To add search capability to your Ti.UI.ListView
, you must populate the Ti.UI.ListItem.searchableText
property with specific text for the row to be search. In this app, it is populated as follows:
// app/controllers/directory.js
{
template: isFavorite ? “favoriteTemplate” : “userTemplate”,
properties: {
searchableText: item.firstName + ’ ’ + item.lastName + ’ ’ + item.company + ’ ’ + item.email,
user: item,
editActions: [
{title: isFavorite ? “- Favorite” : “+ Favorite”, color: isFavorite ? “#C41230” : “#038BC8” }
],
canEdit:true
},
userName: {text: item.firstName+” “+item.lastName},
userCompany: {text: item.company},
userPhoto: {image: item.photo},
userEmail: {text: item.email}
};
The above is a subset of the actual code. The object represents the data passed into the ListItem. In this case we are passing multiple text strings into the searchableText
property representing all of the text noted on the ListItem. This ensures that searching for any of the text on the ListItem returns all rows that match.
From the directory screen, you can also access your Favorites by clicking on the associated icon (★ for android, a book for iOS). This button along with the search bar are implemented differently for iOS and Android.
<!--
Menu Item to open the bookmarks view
-->
<Menu id="menu" platform="android">
<!--
Adding the SearchView to the ActionBar via the Menu feature. Leveraging the standard Android Resource for the search
icon. Uses shared onChange function with iOS to update listView search text.
-->
<MenuItem showAsAction="Ti.Android.SHOW_AS_ACTION_IF_ROOM" title="Search" icon="Ti.Android.R.drawable.ic_menu_search">
<ActionView>
<SearchView ns="Ti.UI.Android" id="searchBar" platform="android" onChange="onSearchChange" />
</ActionView>
</MenuItem>
<!-- Android Menu item for accessing the Bookmarks view-->
<MenuItem id="bookmarkBtn" title="Bookmarks" icon="/images/ic_action_action_bookmark.png" onClick="onBookmarkClick" showAsAction="Ti.Android.SHOW_AS_ACTION_IF_ROOM"/>
</Menu>
<SearchBar id="searchBar" platform="ios" class="search" onBookmark="onBookmarkClick" onFocus="onSearchFocus" onCancel="onSearchCancel" onChange="onSearchChange" />
For Android, the icon is included as a MenuItem, while on iOS, the bookmark Icon is actually a system icon on the SearchBar. These elements are both outlined in the view code above, however the tags take advantage of the platform
attribute for specifying which platform the elements should be rendered. One important aspect however is that even though there are two different UI elements used, they are both sharing the same event function, onBookmarkClick
.
iOS
iOS allows for additionally functionality tied to the Ti.UI.ListView
to enhance the overall user experience.
Edit Actions
Swiping left on a ListItem (row) will reveal the associated edit action – Adding or Removing a Favorite. This provides quick capability to the end user streamlining the experience.
Edit Actions are really easy to use, and provide a slick UI element to improve the overall functionality of your app. Much like adding search text to your ListView, on the row properties, you can include an editActions
property with an array of objects that represent the actions that are available.
// app/controllers/directory.js
editActions: [
{title: isFavorite ? "- Favorite" : "+ Favorite", color: isFavorite ? "#C41230" : "#038BC8" }
],
canEdit:true
In the above example code, you can see that we’re checking to first see if the isFavorite
flag is set, indicating whether or not the user is already marked as a favorite, this is needed since this is a dynamic edit action, and we want the title
property to reflect to the user if its adding or removing a favorite. The same is done on the color
property for the edit action.
Note. The
canEdit
property is also set to true, this is necessary for the edit actions to work.
Edit Actions produce their own event, editaction
that is captured on the ListView. To capture the event add an event listener to the listview object as follows:
function onRowAction(e){
var row = e.section.getItemAt(e.itemIndex);
var id = row.properties.user.id;
if(e.action === "+ Favorite") {
$FM.add(id);
}
else {
$FM.remove(id);
}
$.listView.editing = false;
init();
}
// Create the event listener to capture the editaction
$.listView.addEventListener(“editaction”, onRowAction);
Above, you can see that the indexes are created by capturing the first character of the last name of the contact. are creating a function, that is passed into the editaction
event listener. The event object, e
, comes populated with an action
property that is a reference to the title of the action that is clicked. This allows you to maintain several event actions on a row, and functionally react to each of them independently.
For more information on EditActions see the Appcelerator Titanium API Documentation
ListView Indices
iOS also supports a ListView index allowing you to filter the sections based on ListSection header titles. This can be a very useful feature for helping users quickly search for a particular contact.
The corporate directory app has this feature built in, however its hidden by default. You can enable the index by swiping left on any of the ListView section headers to expose the feature. Titanium again makes this easy.
if(OS_IOS) {
indexes.push({
index: indexes.length,
title: group[0].lastName.charAt(0)
});
//... more code was here, removed for clarity :)
$.wrapper.addEventListener("swipe", function(e){
if(e.direction === "left"){
$.listView.sectionIndexTitles = indexes;
}
if(e.direction === "right"){
$.listView.sectionIndexTitles = null;
}
});
}
The code above, creates the indexes by capturing the first character of the last name of the contact. This index array is then used to set the $.listView.sectionIndexTitles
which creates the index for the ListItem. The event listener above captures the swipe event, to expose the index or hide it as needed. To hide the index, notice all that is required is to set the .sectionIndexTitles
to null
.
Screen 3 – Profile Details (profile.xml/tss/js)
The profile details screen provides a view of the contact with all their information available. On this screen you can see the contact’s:
- Name
- Job Title
- Company
- Location
- Phone
- IM Contact
Also users can manage whether or not the contact is a favorite from this screen as well. The below screenshots showcase how this screen looks on both Android and iOS, note that outside of the platform specific UI, the view itself looks almost identical.
Map View
The map is created using the Appcelerator Titanium module, Ti.Map
. The Ti.Map
module is added to the tiapp.xml
under the module section.
<!-- tiapp.xml -->
<modules>
<module platform="android">ti.map</module>
<module platform="iphone">ti.map</module>
</modules>
This can be done both through the GUI as well as by adding the XML directly to the file. Once the Ti.Map
module is added to the project, it can be added to the view code :
<!-- Map View Leverages the Map Module, allowing for v2 of Google Maps and latest codebase unifying Google v2 and IOS --> <Module id="mapview" module="ti.map" method="createView" platform="android,ios,windows" class='no-touch top' /> <View ns="Ti.Map" id="mapview" platform="mobileweb" class='buffer border-dark-thick no-touch top' />
Note. The `platform` attribute is again being leveraged for distinguishing between the native app module (for ios, android and windows) and the MobileWeb usage.
For denoting the contact’s location, a separate view, `annotation.xml` is leveraged to create a customized pin annotation with the contacts avatar image to show location on the map.
<!-- app/views/annotation.xml -->
<Alloy>
<View class="container vgroup size">
<ImageView id="avatar" />
<Label class="icon-carat" />
</View>
</Alloy>
// app/styles/annotation.tss
“#avatar”:{
borderColor: “#038BC8”,
borderWidth: 5,
borderRadius: 37.5,
height: 75,
width: 75,
},
“.icon-carat”:{
font:{
fontSize: 30,
fontFamily:”icomoon”
},
text:”\uf0d7”, // Unicode character representation for the bookmark icon font
color:”#038BC8”,
top:-14,
}
By creating this as a separate view, we can leverage the image information associated with the contact’s avatar to create the view that will act as the custom annotation for the Map. To create the custom annotation, it is a simple matter of creating a new Map annotation object and assigning our annotation view to the customView
property of the Ti.Map.Annotation
object.
// app/controllers/profile.js
var Map = OS_MOBILEWEB ? Ti.Map : require(‘ti.map’), // Reference to the MAP API
// … more code was here but removed for clarity
var mapAnnotation = Map.createAnnotation({
latitude: _args.latitude || 30.631256,
longitude: _args.longitude || -97.675422,
customView: Alloy.createController(“annotation”, {image: _args.photo}).getView(),
animate:true
});
/**
* Add the Map Annotation to the MapView
*/$.mapview.addAnnotation(mapAnnotation);
In the above code, we are using the map module to create the annotation object passing in the necessary coordinates, and then using the Alloy.createController
to specify the annotation view for customizing the look of the annotation. The annotation view takes a javascript object as a parameter to receive the contact’s avatar image reference.
Font Based Icons
While the rest of the view layout is pretty straightforward, labels with text, its worth noting a technique for representing images that is very beneficial to mobile apps in general and especially useful for cross-platform. That is the use of font based iconography in your app.
Font Icons provide many benefits to your application that normal images do not.
- They are extremely lightweight, a library of 500 icons might be 70kb total in your app, while using images could result in MB’s of size when accommodating all form factors, screen resolutions etc.
- They are scalable. No need to worry about resolution as fonts are vector based, they can scale without any loss of quality.
- Color change is easy – like any label, simply by changing the font color you can quickly change the context of the button etc.
- Flexible – Font Icons can easily be combined with text so are usable in TextFields, Labels and TextArea objects.
- Cross-Platform Friendly! Everything looks great across any OS.
The entire application leverages font icons, for example the Favorites in the directory view, however we see it best leveraged here on the Profile Details View.
The three major buttons for interacting with the Contact are all styled Ti.UI.Labels
with their text property set to the correct unicode character to reference the icon of choice. All of these are predefined within the stylesheet app/styles/profile.tss
and are referenced as class on the Label object using the icon icon-*
representation. Predefining the icons as a Titanium style class dramatically improves your ability to access the right unicode character from the custom font.
// app/styles/app.tss /** * Action Button Styles * These styles represent unicode character codes for the icon fonts * being used to represent the icons on the profile view */“.icon-phone”:{ text: “\uf095” }, “.icon-email”:{ text:”\uf0e0” }, “.icon-message”:{ text:”\uf075” }, “.icon-calendar”:{ text: “\uf073” }, “.icon-btn”:{ height: 60, width: 60, color: “#C41230”, borderRadius: 30, borderWidth: 2, borderColor: “#C41230”, backgroundColor: “#33C41230”, textAlign: “center”, font:{ fontSize: 24 }, left:”10%” },
Form more information about using custom fonts within your app, please see the Appcelerator Titanium SDK Documentation
Wrap Up
This is just one example of a 100,000’s of cross-platform apps that have been built on Appcelerator. With this walkthrough, you should have a good understanding of how Appcelerator Titanium can make development of cross-platform mobile apps quick and easy. Streamlining the overall development process and maintenance long-term for the application development organizations that range from individuals to large enterprise groups.