Skip to content
Bert Grantges edited this page Mar 7, 2016 · 17 revisions

Appcelerator Studio ships with many samples for new developers to leverage as learning tools. The Corporate Directory app, showcases cross platform development, benefits of Titanium Stylesheets and Alloy Backbone / Models.

This exercise will be 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 application, only two major screens. However we wanted to make sure and leverage a cross platform navigation strategy that could be leverage no matter how many screens are present in the application. This is simple to accomplish and provides a basis for create a great apps for iOS, Android and Windows, and still using native platform design paradims.

In the index.js, you will find a global object, Alloy.Globals.Navigator. This object can be accessed from anywhere in the application, and allows the developer to quickly open new windows on to the existing navigation stack. iOS does handle the window stack a bit differently, as does MobileWeb and so we have to make some minor modification to leverage the platform specific pieces in the index.xml.

<!-- app/views/index.xml -->

<Alloy>
	<!-- Default App Window -->
	<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 that 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 navigatin is also broken into platform specific code, but abstracting 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 it is 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 application leverages 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 recomendation does not seem to impact the ability to submit to the app store, be aware of this best practice.

https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/LaunchImages.html

Screen 1 - Loading Animation (loader.xml/tss/js)

Included in this example app is a loading animation of a rocket blasting off. While all data is actually shipped with the sample, and there was not a need to actually load data here, it was included as an example of 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 ImagView 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.ANIMATION_CURVE_EASE_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.ANIMATION_CURVE_EASE_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 additionaly 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
  • Email
  • 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 :

<!-- app/views/profile.xml -->

<!--
	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 straight forward, 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 accomodating 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, Lables 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 applications that have been built on Appceleartor. With this walkthrough complete, you should have a good understanding of how Appcelerator Titanium can make development of cross platform mobile applications quick and easy. Streamlining the overall development process and maintenance long term for the application development organizations that range from individuals through large enterpraise groups.