Countism

Countism App Icon

Countism is a mobile app idea that I have been working on and thinking about for a long time. Even though I have been thinking of the idea for a while and even did some initial work, I did not plan to actually build and release the app – that was always a question that was up in air. My initial work was really just experimental to see if it was something I could build easily enough to make it worth the effort, and to see if my idea would be viable in app form.

Continue reading

Titanium Proxy Objects With JavaScript Call and Apply

Sometimes language and platform abstractions bite you. They can seem pretty straightforward and “just like the real thing”, but sometimes they have odd behaviors that don’t quite work the way you expect.

Dynamic method invocation with .call() and .apply()

The .call() and .apply() functions are something you will use a lot if your code involves a lot of dynamic arguments, such as building an array of arguments that then need to be mapped to actual ordered function arguments in a function or method call.

Since Titanium runs native JavaScript with the V8 engine bundled in your app, you will naturally find yourself attempting things like this:

var args = [sql].concat(params);
var db = Ti.Database.open('dbname');
var rs = db.execute.apply(db, args);

But if you run that, and you will be presented with the head-scratching error:

invalid method 'execute:'

Although the error is pretty confusing – you know there is a method named execute on the db object – there actually is a good explanation for this behavior.

Titanium Proxy Objects

What intiutively seems to you like a normal JavaScript variable here – db – is actually a proxy object created and returned by Titanium with the Ti.Database.open call. Titanium also returns proxy objects for pretty much every other component that has a native counterpart they are bridging over to behind the scenes. This black magic is fundamental to how Titanium works internally.

The Solution

Although the main Titanium proxy object explanation suggests creating a wrapper object for the method call, that’s actually not necessary at all, and creates a lot of extra work, as well as many additional lines of code.

The best solution is to use a bit of JavaScript meta programming and call the apply method directly from the Function prototype:

var args = [sql].concat(params);
var db = Ti.Database.open('dbname');
var rs = Function.prototype.apply.call(db.execute, db, args);

This is a nice one-liner that achieves the same end result, is much easier to undertand, and does not require a wrapper object or another layer of indirection to achieve.

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.

php|tek 2011

php|tek 2011 Speaker Chicago – May 24-27 php|tek in Chicago was fun as always. It is the best PHP conference I have ever been to, which makes sense, given that it is focused solely on PHP and surrounding technologies. The best thing about the conference is the community feeling in general. You get a real sense that everyone there really cares about PHP and is heavily invested in it, which is good for moving the whole language and ecosystem forward.

I myself gave two talks – one about Stackbox CMS, a new CMS project I have been working on, and one about Apppcelerator Titanium Mobile since I have been working with it a lot lately. The presentations are embedded below.

Continue reading

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