UPDATE: this blogpost has been updated to use the latest version of JayData (1.1.1). The example project has been moved to a separate GitHub repo: https://github.com/jaydata/titanium-odata-client. Feel free to fork it and improve the app with your ideas.
As we are determined to unify the data management in HTML5 and JavaScript, we try to support as many platforms as we can. After developing hybrid PhoneGap applications using JayData, we decided to make a try to make a proof-of-concept with Appcelerator Titanium.
It was clear that supporting local data storage access to Titanium developer will take a while for us, so we focus on how to manage remote cloud data in Titanium apps using JayData, hence we decided to put together an OData client application, which queries the online Netflix movie data service. We have already published a similar Netflix browser example to Sencha Touch 2.
The example application
Our example Titanium project will use JayData to access the public Netflix movie database with a single query. JayData is a provider-based library, developers can manage CRUD data in local storages (WebSQL/SQLite, IndexedDB) and on the cloud (Facebook, OData, YQL) with the same syntax. At the moment we tested JayData in Titanium environment only with OData provider.
The user interface will be very simple, we will render two tabs:
- Master view – lists the last 50 movies
- Detail view – displays the selected movie from the list
As I’m not an expert of Titanium, so I won’t be able to show a masterpiece application here, but I will demonstrate how can you use JayData for data management in cross-platform scenario. You will see that the source-code of data management is the same for Android and iOS.
Putting the app together
First of all, you will need the latest JayData library. You can fork and build the latest (non-stable) version for yourself from the JayData Github repo, or download the released version from the JayData CodePlex project page.
No matter how you get JayData, you will have a jaydata.js and a jaydataproviders subfolder. As we will use only the OData provider, we can save same space by deleting all the files in the jaydataproviders folder, except the oDataProvider.js, which contains the necessary dependency (data.js). Before you include JayData to the Resources folder of your Titanium Studio project, make sure you have saved the files in UTF-8 format.
Now we can create a new Titanium Mobile Project in Titanium Studio by following the project creation wizard.
The Netflix OData service metadata can be analyzed here: http://odata.netflix.com/v2/Catalog/$metadata. As you can see, this metadata describes the published entities (Title, Person, BoxArt, etc.) and the relationships.
Without JayData we would build a querystring, use a XMLHttpRequest in web applications or Ti.Network.HTTPClient in Titanium to query query the service URL, process the result and map the object properties to the frontend. JayData does the first three labour-intensive tasks for us and helps the UI mapping with navigable object references. JayData is shipped with JaySvcUtil, which generates the client-side metadata of online OData services as a context definition. Currently JaySvcUtil can be used only on Windows platform (any contribution is welcome, guys), but you find the generated netflix.js.
After you have this context (and JayData) loaded to your project, you are able to use JavaScript Language Query (JSLQ) syntax to access Netflix data.
Building the frontend
As I mentioned, we will have the master and the detail views as two element of a TabGroup control.
After the application starts, Titanium gets the matching ApplicationWindow.js, depending on the platform of the current device/emulator. This ApplicationWindow class initiates the TabGroup containing our two views.
To overcome the differences of iOS and Android, our current implementation we will reference JayData differently.
iOS:
- We can use require() to load JayData library and the Netflix context definition in the ApplicatonWindow class
- Let’s tell JayData to use the Titanium-specific HTTPClient to issue requests by injecting the Ti.Network.HTTPRequest
- According to the Titanium SDK API Reference, HTTPClient class doesn’t implement the getAllResponseHeaders() method on iOS platform, so we have to monkey patch it here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function ApplicationWindow() { // Include JayData, OData provider and the pre-generated Netflix context definition require('jaydata'); require('jaydataproviders/oDataProvider'); require('netflix'); //set default values for JayData OData provider window.XMLHttpRequest = function(){ var xhr = Ti.Network.createHTTPClient(); xhr.getAllResponseHeaders = function(){ return ''; }; xhr.timeout = 20000; return xhr; }; // -------- Begin Your Application --------- } |
Android:
- I wasn’t able to use require by overcoming the issues of global scope and window object, so I use Ti.include() to load JayData library and the Netflix context in each view as a workaround. Please keep me posted if you find any better, working solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function MasterView() { //workaround in each views //load jaydata library, OData provider and the netflix context definition if (Ti.Platform.osname == 'android'){ Ti.include(Ti.Filesystem.resourcesDirectory + 'jaydata.js'); Ti.include(Ti.Filesystem.resourcesDirectory + 'jaydataproviders/oDataProvider.js'); Ti.include(Ti.Filesystem.resourcesDirectory + 'netflix.js'); //inject the Titanium HTTPClient class to JayData in order to use it for requests (instead of XMLHttpRequest) window.XMLHttpRequest = Ti.Network.HTTPClient; } // --- begin your view --- } |
The ApplicationWindow defines an event listener, which is attached to the click event of the movie list and fires an event in the detail view by passing the ID of the selected movie.This also switches to the detail view.
1 2 3 4 5 6 |
masterView.addEventListener('click', function(e) { detailView.fireEvent('showMovieDetail', { movieId: e.row.movieId }); self.setActiveTab(1); }); |
Master view
We will write here a JSLQ query to retrieve the list of movies, create a TableView and add a TableViewSection for each movie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
var tableData = []; //begin JayData query Netflix.context.Titles .orderByDescending(function (movie) { return movie.AverageRating; }) .take(50) .toArray(function(result) { //end of JayData query result.forEach(function (movie) { var section = Ti.UI.createTableViewSection({ headerTitle: movie.Name + '( ' + movie.ReleaseYear + ' )' }); var row = Ti.UI.createTableViewRow({ movieId: movie.Id }); row.add(Ti.UI.createImageView({ image: movie.BoxArt.SmallUrl, width: 65, left: 5, top: 5 })); row.add(Ti.UI.createLabel({ color: '#666666', text: movie.ShortSynopsis, left: 75, top: 5 })); section.add(row); tableData.push(section); }) var table = Ti.UI.createTableView({ data: tableData}); self.add(table); }); |
Notice that we hadn’t made any manual AJAX call, we haven’t build a query string and we haven’t parse any response because JayData has solved all of these tasks for us.
Detail view
We can arrive to the detail view by choosing a movie on the master view.
Here we will build a little bit more complex JSLQ statement to retrieve the selected movie. The query consists of two major parts:
- filter: we ask for the movie with specific value
- include: we join the movie (Title entity) with the cast to retrieve the actors as well. Feel free to extend this list with more entities, for example, to show the awards.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Netflix.context.Titles .filter(function(it){ return it.Id == this.id; }, { id: movieId }) .include('Cast') .toArray(function(movies){ var movie = movies[0]; var tableData = []; var titleRow = Ti.UI.createTableViewRow(); titleRow.add(Ti.UI.createLabel({ text: movie.Name, left: 5, top: 5, color: '#000000', font: { fontSize: 24} })); tableData.push(titleRow); var imageRow = Ti.UI.createTableViewRow(); imageRow.add( Ti.UI.createImageView({ image: movie.BoxArt.SmallUrl, width: 65, left: 5, })); imageRow.add(Ti.UI.createLabel({ text: movie.ShortSynopsis, left: 75, top: 5, color: '#555555', font: { fontSize: 14} })); tableData.push(imageRow); var castRow = Ti.UI.createTableViewRow(); castRow.add(Ti.UI.createLabel({ text: 'Cast', left: 5, top: 5, color: '#000', font: { fontSize: 16} })); var castArray = []; movie.Cast.forEach( function(person) { castArray.push(person.Name )}); castRow.add(Ti.UI.createLabel({ text: castArray.join(), left: 50, top: 5, color: '#555555', font: { fontSize: 14} })) tableData.push(castRow); // ... }); |
Our Titanium app now queries the Netflix service using JayData
I assume that everybody reading this blogpost understands Titanium better then me, so I omitted the Titanium UI building. To get a working copy of the source-code, import my example project from GitHub to your Titanium workspace. I tested the iOS platform on OSX and the Android platform on Linux.
Possible improvements
- The user interface 🙂
- You can use the map() function to apply a projection on the movies to retrieve only a subset of the fields in order to reduce network traffic – You can check out the possibilities in JSLQ 101.
- You can improve you master view with custom ordering (for example by movie release year), search and pull-to-refresh features.