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
    Responsive and Infinitely Scalable JS Animations

    Back in late 2012 it was not easy to find open source projects using requestAnimationFrame() - this is the hook that allows Javascript code to synchronize with a web browser's native paint loop. Animations using this method can run at 60 fps and deliver fantastic...

  • By
    I’m an Impostor

    This is the hardest thing I've ever had to write, much less admit to myself.  I've written resignation letters from jobs I've loved, I've ended relationships, I've failed at a host of tasks, and let myself down in my life.  All of those feelings were very...

Incredible Demos

  • By
    “Top” Watermark Using MooTools

    Whenever you have a long page worth of content, you generally want to add a "top" anchor link at the bottom of the page so that your user doesn't have to scroll forever to get to the top. The only problem with this method is...

  • By
    Unicode CSS Classes

    CSS class name structure and consistency is really important; some developers camelcase classnames, others use dashes, and others use underscores.  One thing I've learned when toying around by HTML and CSS class names is that you can actually use unicode symbols and icons as classnames.

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!