Introducing MooTools Cache
MooTools Cache is a new proof of concept plugin that I've created so that developers may easily "cache" values. Cache allows you to set, get, and refresh values. Cache also features a system of reading and writing from Cookies so that the cache remains persistent throughout different pages of your website.
The MooTools JavaScript
var Cache = new Class({ options: { autoRefresh: true, cookieName: 'cache-keys', cookieOptions: {}, cookiePrefix: 'cache-', duration: 60, //seconds sep: ':://::' }, Implements: [Options], initialize: function(options) { this.setOptions(options); this.data = {}; }, set: function(key,value,duration,fn) { this.data[key] = { value: value, expires: this.calculateDuration(duration || this.options.duration), duration: (duration || this.options.duration) }; if(fn) this.data[key]['fn'] = fn; return this; }, get: function(key) { return this.isFresh(key) ? this.data[key]['value'] : (this.options.autoRefresh && this.data[key]['fn'] ? this.refresh(key) : false); }, isFresh: function(key) { return this.data[key]['expires'] > $time(); }, refresh: function(key) { if(!this.data[key] || !this.data[key]['fn']) return false; this.data[key]['value'] = this.data[key]['fn'](); this.data[key]['expires'] = this.calculateDuration(this.data[key]['duration']); return this.data[key]; }, load: function(hash,duration) { for (var key in hash){ if (hash.hasOwnProperty(key)) { this.data[key] = { value: hash[key], expires: this.calculateDuration(duration || this.options.duration), duration: (duration || this.options.duration) }; } } }, loadCookie: function() { var cookie = Cookie.read(this.options.cookieName); if(cookie) { var keys = cookie.split(this.options.sep); keys.each(function(key) { var val = Cookie.read(this.options.cookiePrefix + '' + key); if($defined(val)) { var split = val.split(this.options.sep); this.data[key] = { value: split[0], expires: split[1], duration: split[2] }; if(split[3]) this.data[key]['fn'] = split[3]; } },this); } }, save: function() { //write individual items var keys = []; for (var key in this.data){ if (this.data.hasOwnProperty(key)) { Cookie.write(this.options.cookiePrefix + key,this.data[key]['value'] + this.options.sep + this.data[key]['expires'] + this.options.sep + this.data[key]['duration'] + (this.data[key]['fn'] && $type(this.data[key]['fn']) == 'string' ? this.data[key]['fn'] : ''),this.options.cookieOptions); keys.push(key); } } cookies = keys.join(this.options.sep); //write grouper Cookie.write(this.options.cookieName,cookies); return this; }, clear: function(key) { if(key) { this.data[key] = ''; } else { this.data = {}; } return this; }, /** PRIVATE **/ calculateDuration: function(seconds) { return seconds * 1000 + $time(); } });
Options for Cache include:
- autoRefresh: (defaults to true) Determines whether a value should auto-refresh if the value is stale.
- cookieName: (defaults to 'cache-keys') The name of the "index" cookie that the class will use to store variable keys.
- cookieOptions: (defaults to {}) The options to be given to Cookies written by the class.
- cookiePrefix: (defaults to 'cache-') The prefix given to cookie keys. Useful to prevent overwriting of other cookies.
- duration: (defaults to 60) The default number of seconds to cache a given value. The duration may also be set on a per-key basis.
- sep: (defaults to ':://::') The separater to split the stored cookie information (value, expires, duration, fn).
Public methods for Cache include:
- set(key, value, duration *optional*, fn *optional*): The key and value are simple. The duration argument overrides the default Class duration. The function argument allows you to assign a function to execute to "refresh" a value when it expires.
- get(key): Returns the value for the given key. If autoRefresh is on and fn was passed in initialy, returns the new value.
- isFresh(key): Returns true or false based upon whether or not the value is fresh.
- refresh(key): Refreshes the value of a key.
- load(hash,duration): Takes a provided Object, recurses through it and adds the key->values to the cache.
- loadCookie(): Loads the key->values from cookies.
- save(): Saves the cache to cookies.
- clear(key *optional*): Clears a given key from the cache or the entire cache.
Sample MooTools Cache Usage
Creating an instance is easy:
var cache = new Cache({ duration: 3600 //one hour });
Now we can add and retrieve keys and values to the cache:
//setting cache.set('name','David'); //explicit cache.set(age,$('age').get('value')); //from form cache.set('time',getAJAXInfo(),1200,'getAJAXInfo'); //refresh function //getting if(cache.get('name')) { alert('Welcome back: ' + cache.get('name') + '!'); }
Before we leave the page, save cache to cookies:
cache.save();
...and load values when a page loads:
cache.loadCookie();
Have any ideas for the class? Let me know!
Nice idea, one idea for the code: Maybe, instead of using (very fancy) separator, just encode the values as JSON (like Hash.Cookie does, which you could extend maybe).
Why don’t you use JSON to store the information in the cookie, so you wouldn’t need to parse the values by hand, just JSON.encode/JSON.decode.
@Harald Kirschner: Yeah extend Hash.Cookie would be even smarter, but it’s part of “more” which means more dependency.
Not requiring Hash was a goal so I want to keep that out. Dependencies FTL!
@David Walsh: You can/should still use JSON.
@David Walsh: Hash, shorter code and better readability FTW ;-) Also your class is mostly code that Hash.Cookie already provides, a bold argument for “Extend: Hash.Cookie”
@Harald Kirschner: I see your point. I’ll look into that.
@thomasd: I fear json-econding will bloat the cookie sizes and restrict the number of cookies available to create.
Interesting concept. It may even make cache and cookie managing easier… I wish I had more time to read the code entirely, but I shall do that later…
Until then, keep up the good work.
@David Walsh: I see your point when saving the value as an object, but if you use JSON to save the value as an array, you even save some characters:
Example using your sep:
key:://::hello world:://::123456:://::60:://::myfunc
Example using JSON to encode an array:
["key","hello world",123456,60,"myfunc"]
And you still just need one function call (JSON.decode) to get an full functional JS-Array. And with
you have the full object back again, all without using the Hash-Native.
Another argument for extending Hash.Cookie is that your class requires the developer to cache.save() whereas the values are saved immediately with Hash.Cookie…
The problem with string separators are that there’s always a chance that they might actually occur in a value…which will then corrupt the data.
@Adriaan: Yep, that was where I was hoping I could get some feedback. I think I’ll try the JSON encode/decode.
@Adriaan – why not use control characters as separators…? \c + \a make great separators, and they are far less likely to appear in a string…
@Chris the Developer: far less likely yes, but still possible…with JSON you’re safe.
You could protect() your private function, instead of keeping a comment about it being private.
+1 for JSON encoding/decoding
+2
Nice class though. It’s been so long since I even touched MooTools now after been an early advocate, with all my workplaces insisting on jQuery, it’s nice to keep being reminded to check it out again by your blog.