Introducing MooTools Cache

By  on  

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!

Recent Features

  • By
    Create a CSS Flipping Animation

    CSS animations are a lot of fun; the beauty of them is that through many simple properties, you can create anything from an elegant fade in to a WTF-Pixar-would-be-proud effect. One CSS effect somewhere in between is the CSS flip effect, whereby there's...

  • By
    Create a CSS Cube

    CSS cubes really showcase what CSS has become over the years, evolving from simple color and dimension directives to a language capable of creating deep, creative visuals.  Add animation and you've got something really neat.  Unfortunately each CSS cube tutorial I've read is a bit...

Incredible Demos

  • By
    CSS Tooltips

    We all know that you can make shapes with CSS and a single HTML element, as I've covered in my CSS Triangles and CSS Circles posts.  Triangles and circles are fairly simply though, so as CSS advances, we need to stretch the boundaries...

  • By
    Using Dotter for Form Submissions

    One of the plugins I'm most proud of is Dotter. Dotter allows you to create the typical "Loading..." text without using animated images. I'm often asked what a sample usage of Dotter would be; form submission create the perfect situation. The following...

Discussion

  1. 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).

  2. 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.

  3. @Harald Kirschner: Yeah extend Hash.Cookie would be even smarter, but it’s part of “more” which means more dependency.

  4. Not requiring Hash was a goal so I want to keep that out. Dependencies FTL!

  5. @David Walsh: You can/should still use JSON.

  6. @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”

  7. @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.

  8. 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.

  9. @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

    JSON.decode(jsonified_string).associate(["key", "value", "expires", "duration", "fn"]);
    

    you have the full object back again, all without using the Hash-Native.

  10. 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…

  11. 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.

  12. @Adriaan: Yep, that was where I was hoping I could get some feedback. I think I’ll try the JSON encode/decode.

  13. @Adriaan – why not use control characters as separators…? \c + \a make great separators, and they are far less likely to appear in a string…

  14. @Chris the Developer: far less likely yes, but still possible…with JSON you’re safe.

  15. You could protect() your private function, instead of keeping a comment about it being private.

  16. Alex

    +1 for JSON encoding/decoding

  17. +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.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!