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