Easier Titanium XHR and AJAX Requests

One question I see a lot on the Appcelerator Titanium Developer Q&A is how to perform AJAX requests and/or work with APIs, etc. There is a built-in way to do this with the Ti.Network.HTTPClient module that is pretty easy, but it does have a few drawbacks and “gotchas”, like executing the “success” event for ANY returned status code – even 500 errors. Since working with APIs is so common with mobile apps, I made a wrapper function modeled after jQuery’s $.ajax method that I use in all my apps. It shortens the syntax quite a bit and is much more familiar to those who are used to using jQuery.

The usage looks like this:

// Geocode input text location
app.utils.ajax({
     url: 'http://maps.googleapis.com/maps/api/geocode/json?address=' + txtLocation.value +'&region=us&sensor=true',
     method: 'get',
     success: function(xhr) {
         var data = JSON.parse(xhr.responseText);
         Ti.API.info(data);

         if("OK" == data.status) {
             var res = data.results[0];
             if(res) {
                 alert("Location: " + res.geometry.location.lat + ', ' + res.geometry.location.lng);
             }
             // Do something with coordinates
         } else {
             Ti.UI.createAlertDialog({
                 title: 'Geocode Error',
                 message: 'Unable to geocode location input'
             }).show();
         }
     },
     error: function(xhr) {
         Ti.UI.createAlertDialog({
             title: 'Geocode Error',
             message: 'No location matches found. Please try something else.'
         }).show();
     }
});

And the actual code for the utility function:

/**  * Application Utilities and Helper Methods  **/
(function(_app) {
     _app.utils = {};

     // AJAX method that mimmicks jQuery's
     _app.utils.ajax = function(_props) {
          // Merge with default props
         var o = _app.combine({
             method: 'GET',
             url: null,
             data: false,
             contentType: 'application/json',

             // Ti API Options
             async: true,
             autoEncodeUrl: true,

             // Callbacks
             success: null,
             error: null,
             beforeSend: null,
             complete: null
         }, _props);

         Ti.API.info("XHR " + o.method + ": \n'" + o.url + "'...");
         var xhr = Ti.Network.createHTTPClient({
             autoEncodeUrl: o.autoEncodeUrl,
             async: o.async
         });

         // URL
         xhr.open(o.method, o.url);

         // Request header
         xhr.setRequestHeader('Content-Type', o.contentType);

         if(o.beforeSend) {
             o.beforeSend(xhr);
         }

         // Errors
         xhr.setTimeout(10000);
         xhr.onerror = function() {
             Ti.API.info('XHR "onerror" ['+this.status+']: '+this.responseText+'');
             if(null !== o.error) {
                 return o.error(this);
             }
         };

         // Success
         xhr.onload = function() {
             // Log
             Ti.API.info('XHR "onload" ['+this.status+']: '+this.responseText+'');

             // Success = 1xx or 2xx (3xx = redirect)
             if(this.status < 400) {
                 try {
                     if(null !== o.success) {
                         return o.success(this);
                     }
                 } catch(e) {
                     Ti.API.info('XHR success function threw Exception: ' + e + '');
                     return;
                 }

             // Error = 4xx or 5xx
             } else {
                 Ti.API.info('XHR error ['+this.status+']: '+this.responseText+'');
                 if(null !== o.error) {
                     return o.error(this);
                 }
             }
         };

         // Send
         if(o.data) {
             Ti.API.info(o.data);
             xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
             xhr.send(o.data);
         } else {
             xhr.send();
         }

         // Completed
         if(null !== o.complete) {
             return o.complete(this);
         }
     };

     // And it does depend on this code below to combine object properties:

     // Extend an object with the properties from another
     // (thanks Dojo - http://docs.dojocampus.org/dojo/mixin)
     var empty = {};
     function mixin(/*Object*/ target, /*Object*/ source){
         var name, s, i;
         for(name in source){
             s = source[name];
             if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){
                 target[name] = s;
             }
         }
         return target; // Object
     };
     _app.mixin = function(/*Object*/ obj, /*Object...*/ props){
         if(!obj){ obj = {}; }
         for(var i=1, l=arguments.length; i<l; i++){
             mixin(obj, arguments[i]);
         }         return obj; // Object
     };

     // Create a new object, combining the properties of the passed objects with the last arguments having
     // priority over the first ones
     _app.combine = function(/*Object*/ obj, /*Object...*/ props) {
         var newObj = {};
         for(var i=0, l=arguments.length; i<l; i++){
             mixin(newObj, arguments[i]);
         }
         return newObj;
     };
})(app);

The particular organization of this utilities file assumes that your app structure follows the organization model shown in the Tweetanium example app (using the ‘app’ namespace within a single window context), but is easy to adapt if you are not.

Android+iPhone SEO App

I just released a new iPhone SEO app and Android SEO app called SEMTab SEO Pro. The basic idea is to keep a list of domains saved, and check SEO/SEM stats like Google PageRank (PR), backlinks, Alexa rank, etc. and Social share information from Twitter, Facebook, and Delicious. more

ss1-listss2-webss3-social1

 

The first two pictures are of the iPhone app, and the last one is of the Android app. SEMTab SEO Pro was built with  Titanium using all native cross-platform UI controls, so it builds both the iPhone and Android app from a single codebase and a single development effort.

The nice thing about SEMTab is that it makes extensive use of Titanium’s event system to fire off simultaneous asynchronous HTTP requests to the various web services and APIs to fetch data about the current domain you are checking. This prevents the application from locking up while fetching rank or share information, and it prevents the HTTP requests from stacking up in a queue and waiting for the ones in front of it to finish. The end result is a pretty slick & simple SEO app that gets the results you want quickly, without feeling sluggish or unresponsive.

Check out SEMTab SEO Pro in the App Store or the Android Market when you get the chance — and don’t forget to leave some feedback with a rating.

Zero to App in Two Weeks with Titanium

Like any web developer who has been sitting on the sidelines watching this mobile explosion happen in front of my eyes, I was eager to find a way to jump in. Up until about a month ago, I was still evaluating various different mobile development platforms – Titanium, PhoneGap, and Rhomobile, trying to decide which one I wanted to use to make my first mobile app. My only selection criteria was that the platform would have to be able to produce both iPhone and Android apps with the same code, because I wanted to be able to develop and release both an Android and iPhone apps without having to learn two completely different programming languages and development environments, and without having to do everything twice.
Continue reading