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

  • By
    Write Better JavaScript with Promises

    You've probably heard the talk around the water cooler about how promises are the future. All of the cool kids are using them, but you don't see what makes them so special. Can't you just use a callback? What's the big deal? In this article, we'll...

Incredible Demos

  • By
    Introducing MooTools ScrollSpy

    I've been excited to release this plugin for a long time. MooTools ScrollSpy is a unique but simple MooTools plugin that listens to page scrolling and fires events based on where the user has scrolled to in the page. Now you can fire specific...

  • By
    Scrolling “Agree to Terms” Component with MooTools ScrollSpy

    Remember the good old days of Windows applications forcing you to scroll down to the bottom of the "terms and conditions" pane, theoretically in an effort ensure that you actually read them? You're saying "No David, don't do it." Too late -- I've done...

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!