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));
}
};