Just to announce although a bit late for April, but I’ve recently wrote two articles for JSMag. I highly recommend JSMag to any JavaScript developers who may be reading this.
My first article in April details with using on-demand JavaScript loaded with ExtJS - http://www.jsmag.com/main.issues.description/id=31/
The second article, for May details implementing client-side caching of JavaScript - http://www.jsmag.com/main.issues.description/id=32/
I had some spare time this evening so I started to play around and wrote my own client side JavaScript object cache for use with ExtJS. It’s fairly simple to use with get/set functions to call and works in terms of seconds, for example to cache a simple JavaScript object you can do:
Ext.ux.Cache.set("myJsonData",{test: 1234},10);
This would cache it for ten seconds before having it removed. I’ve even implemented some events. To be notified when items expire for example:
Ext.ux.Cache.on("expired",function(cache,removed) {
alert(removed.length + " items expired.");
});
This comes into its own with the likes of AJAX requests, one test scenario:
function dataSuccess(data) {
...
};
if (Ext.ux.Cache.has("data")) {
// Process cached data
dataSuccess(Ext.ux.Cache.get("data"));
} else {
// Request new data
Ext.Ajax.request({
url: "whatever.ashx",
scope: this,
success: function(response,options) {
// Decode data
var data = Ext.decode(response.responseText);
// Cache data for 30 seconds
Ext.ux.Cache.set("data",data,30);
// Process data
dataSuccess(data);
}
});
}
Anyhow enough of the examples here’s the actual code for you to play with:
Ext.ux.CacheEngine = Ext.extend(Ext.util.Observable,{
cache: [],
constructor: function(config) {
this.addEvents({
"added": true,
"updated": true,
"removed": true,
"expired": true
});
Ext.apply(this,config);
var cache_task = {
run: function() {
// Get current time
var now = new Date().getTime();
// Process cache items to remove
var to_remove = [];
for(var i = 0, len = this.cache.length; i < len; i++) {
// Get cached item
var item = this.cache[i];
// Check time
if (item.expires < now) to_remove.push(item);
}
// Remove items
var removed = [];
for(var i = 0, len = to_remove.length; i < len; i++) {
var item = to_remove[i];
var removed_item = {
key: item.key,
value: item.value
};
removed.push(removed_item);
this.cache.remove(item);
}
// Fire event
if (removed.length > 0) this.fireEvent("expired",this,removed);
},
interval: 1000,
scope: this
};
Ext.TaskMgr.start(cache_task);
// Call our superclass constructor to complete construction process.
Ext.ux.CacheEngine.superclass.constructor.call(config)
},
clear: function()
{
this.cache = [];
},
get: function(key)
{
// Get item
for(var i = 0, len = this.cache.length; i < len; i++) {
var item = this.cache[i];
if (key == item.key) return item.value;
}
// Return
return null;
},
set: function(key,value,timeout)
{
timeout = timeout || 10;
// Find item
for(var i = 0, len = this.cache.length; i < len; i++) {
var item = this.cache[i];
if (key == item.key) {
// Update item
item.value = value;
item.expires = new Date().getTime() + (timeout * 1000);
// Fire event
this.fireEvent("updated",this);
// Return
return;
}
}
// Add new item
var item = {
key: key,
value: value,
expires: new Date().getTime() + (timeout * 1000)
};
this.cache.push(item);
// Fire event
this.fireEvent("added",this);
},
remove: function(key)
{
// Find item
for(var i = 0, len = this.cache.length; i < len; i++) {
var item = this.cache[i];
if (key == item.key) {
// Remove item
this.cache.remove(item);
// Fire event
this.fireEvent("removed",this);
// Return
return true;
}
}
// Return
return false;
},
has: function(key)
{
// Look for key
for(var i = 0, len = this.cache.length; i < len; i++) {
// Get item
var item = this.cache[i];
// Compare keys
if (key == item.key) return true;
}
// Return
return false;
}
});
Ext.ux.Cache = new Ext.ux.CacheEngine();
I’m going to attempt to create a cached DataProxy and DataReader at some point, that should provide a more useful feature for ExtJS users so they can plug it in directly into existing code.
I’ve been working on implementing caching into some of our products to increase efficiency and speed things up, however I came to a sticking point recently in that our products are often deployed to widely different platforms that don’t all offer the same feature set. This meant that caching on some systems didn’t work the same or at all on others.
Mulling over this today I decided upon a common cache engine that I could plug various cache providers into for available features allowing me to change which caching system to use depending on the platform without having to alter vast amounts of code.
I constructed a Cache class with static methods, a general outline of the class is as follows:
public class Cache
{
public static void Initialize();
public static void Uninitialize();
public static bool Use;
public static void RegisterProvider(ICacheProvider provider);
public static void UnregisterProvider(ICacheProvider provider);
public static object Get(string key);
public static void Set(string key, object value);
public static void Set(string key, object value, int minutes);
public static void Set(string key, object value, DateTime dt);
public static void Set(string key, object value, TimeSpan ts);
public static void Unset(string key);
public static bool Exists(string key);
public static void Clear();
public static ICacheProvider Default;
public static ICacheProvider Empty;
}
This provides a common way to access a cache rather than individualised access methods. For each individual cache system you write a class that implements ICacheProvider which interfaces between the Cache class and the underlying caching system.
The ICacheProvider interface looks like this:
public interface ICacheProvider
{
void Initialize();
void Unitialize();
object Get(string key);
void Set(string key, object value);
void Set(string key, object value, int minutes);
void Set(string key, object value, DateTime dt);
void Set(string key, object value, TimeSpan ts);
void Unset(string key);
bool Exists(string key);
void Clear();
string Name
{
get;
}
}
As an example implementation take ASP.NET, which provides a cache as part of the HttpRuntime class. I can write an ICacheProvider to interface to it, such as:
public class HttpRuntimeCacheProvider : CacheProvider
{
public override void Initialize()
{
// Do nothing...
}
public override void Unitialize()
{
// Do nothing...
}
public override object Get(string key)
{
return HttpRuntime.Cache.Get(key);
}
public override void Set(string key, object value)
{
DateTime expires = DateTime.UtcNow.AddMinutes(10);
Set(key,value,expires);
}
public override void Set(string key, object value, DateTime dt)
{
DateTime expires = dt.ToUniversalTime();
HttpRuntime.Cache.Insert(key,value,null,expires,System.Web.Caching.Cache.NoSlidingExpiration);
}
public override void Unset(string key)
{
HttpRuntime.Cache.Remove(key);
}
public override bool Exists(string key)
{
object value = Get(key);
return (value != null);
}
public override void Clear()
{
List<string> keys = new List<string>();
foreach(DictionaryEntry entry in HttpRuntime.Cache) keys.Add(entry.Key.ToString());
foreach(string key in keys) HttpRuntime.Cache.Remove(key);
}
public override string Name
{
get {
return "HttpRuntime";
}
}
}
We can register and set as default using:
ICacheProvider provider = new HttpRuntimeCacheProvider();
Cache.RegisterProvider(provider);
Cache.Default = provider;
All future requests via Cache will now be handled by the ASP.NET cache.
In the included downloadable source code I’ve wrote providers for ASP.NET, MemCached and my own simple example provider. You can of course take this further and write one to use a database for example.
Below you’ll find the source code to download. I’ve used a custom build of a .NET MemCached client which I’ve also included a link to download. The original can be found here.
As always comments and suggestions welcome!