Tag Archives: appcelerator

AJAX Caching with Titanium Appcelerator

So I’ve had quite a few questions about how I do AJAX caching in Titanium Appcelerator. So, here’s my solution.

First, I needed some persistent storage with a very general structure. I could have gone with flat files, but I decided to make use of the built-in SQLite engine, so that I could easily access entries and delete old data.

Database

var db_conn = Ti.Database.open('app_db1');
db_conn.execute('CREATE TABLE IF NOT EXISTS `remote_cache`
                 (key TEXT, type TEXT, valid_till TEXT, value TEXT,
                 PRIMARY KEY (key, type))');

Next, I need a wrapper class for my ajax calls. Note the “get” function which is the main function call. Here is the signature and explaination:

get: function (url, params, type, valid_for, callback, skip_cache)

  • url : string : the full url to call
  • params : object : a simple object with name/value parse for POST variables
  • type : string : the cache domain for this call. Usefull for clearing all cache data for a specific domain
  • valid_for : date string : the amount of time the cache should remain valid for
  • callback : function : the function to call on completion or fail of the request
  • skip_cache : boolean : an option to bypass the cache and perform write through

AJAX Class


var ajax =
{
	http_conn: Ti.Network.createHTTPClient(),
	url:'',
	type:'',
	params:{},
	key:'',
	valid_for: '+1 hour',
	callback: null,
	debug:true,
	get: function (url, params, type, valid_for, callback, skip_cache)
	{
		this.key = MD5(url + '|' +   JSON.stringify(params));
		this.url = url;
		this.params = params;
		this.valid_for = valid_for;
		this.type = type;

		this.callback = callback;

		if (ajax.debug) Titanium.API.info('Ajax Call');
		if (skip_cache)
		{
			this.remote_grab();
		}
		else if (!this.local_grab())
		{
			this.remote_grab();
		}

	},
	abort : function ()
	{
		this.http_conn.abort();
	},

	local_grab: function ()
	{
		if (ajax.debug) Titanium.API.info('Checking Local key:' + this.key);

		var rows = db_conn.execute('SELECT valid_till, value FROM remote_cache WHERE key = ? AND type = ? AND valid_till > datetime(\'now\')',  this.key, this.type);//, '', '+this.valid_for+')');//',  this.key, 'date(\'now\', '+this.valid_for+')');
		var result = false;
		if (rows.isValidRow())
		{
			if (ajax.debug) Titanium.API.info('Local cache found with expiration:' + rows.fieldByName('valid_till'));
			var result = JSON.parse(rows.fieldByName('value'));
			rows.close();

			this.callback(result);
			result = true;
		}
		rows.close();
		return result;
	},

	remote_grab: function ()
	{
		if (ajax.debug) Titanium.API.info('Calling Remote: ' + this.url + ' - PARAMS: '+ JSON.stringify(this.params));

		this.abort();
		this.http_conn.setTimeout(10000);

		// catch errors
		this.http_conn.onerror = function(e)
		{
			//alert('Call failed');
			ajax.callback({result:false});
			Ti.API.info(e);
		};

		// open connection
		this.http_conn.open('POST', this.url);

		// act on response
		var key = this.key;
		var valid_for = this.valid_for;
		var callback = this.callback;
		var type = this.type;

		this.http_conn.onload = function()
		{
			if (ajax.debug) Titanium.API.info('Response from server:' + this.responseText);
			if (this.responseText != 'null')
			{
				var response = JSON.parse(this.responseText);
				if (response.result == true || (response && response.length > 0))
				{
					ajax.update_local(key, type, valid_for, response);
					callback(response);
				}
				else if (response.result == false && response.error  && response.error.length > 0)
				{
					callback({result:false,error:response.error});
				}
				else
				{
					callback({result:false,error:'Invalid Result (1)'});
				}
			}
			else
			{
				callback({result:false,error:'Invalid Result (2)'});
			}
		};

		// Send the HTTP request
		this.http_conn.send(this.params);

	},

	update_local: function (key, type, valid_for, response)
	{
		if (ajax.debug) Ti.API.info('Updating Cache: KEY: ' + key + ' TYPE: ' + type +' - FOR: '+valid_for+', ' + JSON.stringify(response));
		db_conn.execute('DELETE FROM remote_cache WHERE (valid_till <= datetime(\'now\') OR key = ?) AND type = ?', key, type);
		db_conn.execute('INSERT INTO remote_cache ( key, type, valid_till, value ) VALUES(?,?,datetime(\'now\',?),?)',key, type, valid_for, JSON.stringify(response));
	}
};

Remote/Live Table Search with Titanium Appcelerator

I recently began using Titanium Appcelerator to build an iPhone app for WorthMonkey. One of the features I love in any application, is the ability to search a table using remote data. However, this is very difficult to pull off in Appcelerator, because it’s table search work only on local data. So here’s how I got around that limitation to make a true remote search table.

First, obviously it quite easy to add a search bar to a table, so I did that then attached a listener on the “change” event. But when you attempt to do your remote call here, the built in filter fires first and overlays the current table, so updating the table contents afterward is of no help. So, the trick is to fire the filter event again, AFTER you’ve filled the table. So on line 70, I tell the filter to fire again, and on line 24 I put a check in to make sure we don’t start infinite calls. Enjoy!

//SEARCH BAR
var search = Titanium.UI.createSearchBar({
	barColor:'#77B121',
	height:43,
	hintText:'What\'s It Worth?',
	top:0
});

//AUTOCOMPLETE TABLE
var table_data = [];
var autocomplete_table = Titanium.UI.createTableView({
	search: search,
	scrollable: true,
	top:0
});
win.add(autocomplete_table);

//
// SEARCH BAR EVENTS
//
var last_search = null;
search.addEventListener('change', function(e)
{
	if (search.value.length > 2 && search.value !=  last_search)
	{
		clearTimeout(timers['autocomplete']);
		timers['autocomplete'] = setTimeout(function()
		{
			last_search =search.value;
			auto_complete(search.value);
		}, 300);
	}
	return false;
});

function auto_complete(search_term)
{
	if (search_term.length > 2)
	{
		var url = 'YOURURL' + escape( search_term);
		var ajax_cache_domain = 'autocomplete';
		var params = {};
		var cache_for = '+7 days';
		ajax.get(url, params, ajax_cache_domain, cache_for, function (response)
		{
			if (typeof(response) == 'object')
			{
				var list = response;
				// Empty array &quot;data&quot; for our tableview
				table_data = [];

				for (var i = 0; i < list.length; i++)
				{
					//Ti.API.info('row data - ' + data[i].value);
					var row = Ti.UI.createTableViewRow(
					{
						height: 40,
						title: list[i].value.replace(/^\s+|\s+$/g,""),
						hasDetail:true
					});

					// apply rows to data array
					table_data.push(row);

				};

				// set data into tableView

				autocomplete_table.setData(table_data);
				search.value = search.value;
			}
			else
			{
				alert(response.error);
			}

		});
	}
}