Skip to content

Instantly share code, notes, and snippets.

@romainl
Forked from plugnburn/README.md
Created March 10, 2016 20:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save romainl/a5663503dadcf1ed7eb3 to your computer and use it in GitHub Desktop.
Save romainl/a5663503dadcf1ed7eb3 to your computer and use it in GitHub Desktop.
Zen.js - reactive nano-framework in 50 lines of JS

Zen.js

It seems that perfection is attained not when there is nothing more to add, but when there is nothing more to take away.

Antoine de Saint Exupéry

Zen.js is tiny attempt to create the Zen of reactive in-memory storage on the JS client-side environment.

How to obtain

Just download the library here or paste this into your HTML:

<script src="//cdn.rawgit.com/plugnburn/295342f92883ff562084/raw/2b7eb5b62140fa5262e905099ac6e3e7185f434a/zen.min.js"></script>

Usage

Basically, Zen.js is a plain JS in-memory object with some additional methods. So you can write or read properties as usual:

Zen.foo = 'bar'
console.log(Zen.foo) // => "bar"

You can import any existing object into Zen namespace by calling Zen.import:

var someObj = {a: 1, b: 2}
Zen.foo = 'bar'
Zen.import(someObj)
console.log(Zen.a, Zen.b, Zen.foo) // => 1 2 "bar"

Additionally, if you pass Zen to any function that requires a string, it will be auto-serialized to JSON in the sorted form:

console.log('' + Zen) // => {"a":1, "b":2, "foo":"bar"}

But the real magic comes when you start defining tracking functions. You can do this by just passing a callback to Zen, and its entire namespace becomes available as this:

Zen(function(){
	console.log('Something changed, let\'s check foo variable:', this.foo)
})

All registered trackers run in a single polling loop when something is changed in Zen namespace. By default, the polling loop checks the changes with an interval of 100 milliseconds. You can override this by calling Zen.interval method:

Zen.interval(1000) // perform checks each second instead of 0.1 s

Last but not least, Zen.js has a req method which allows you to make some HTTP requests (GET if you don't pass the third parameter, and POST if you do) and automatically save the result under some key in Zen namespace:

//...track the values...

Zen(function(){
	if(Zen.remoteData)
		console.log('GET result:', Zen.remoteData)
	if(Zen.apiResult)
		console.log('POST result:', Zen.apiResult)
})

//...and make actual requests at some point

Zen.req('remoteData', '//example.com/data.json') //example of a GET request
Zen.req('apiResult', '//example.com/api.json', JSON.stringify({foo:'bar'})) //example of a POST request

Advanced usage

That's really everything the library has to offer, but with this functionality everything else becomes a breeze.

Persistent storage

We can use auto-serializing and import features to make the entire Zen namespace get stored persistently:

Zen.import(localStorage.getItem('Zen'))
Zen(function(){
	localStorage.setItem('Zen', this)
})

If you want to save only a part under some key myKey, it's just as easy, except you'll have to do the marshaling manually:

Zen.myKey = JSON.parse(localStorage.getItem('myPhysicalKey'))
Zen(function(){
	localStorage.setItem('myPhysicalKey', JSON.stringify(this.myKey))
})

Data binding

Unlike some bloated (or not so bloated but still bigger) frameworks, Zen.js doesn't come with built-in DOM-to-data binding capabilities. However, they are pretty easy to create, especially using a ready DOM manipulation library (such as equally tiny Q.js, for instance).

Data-to-DOM binding example:

RateIndicator = Q('#usd-in-uah')()
Zen(function(){
	if(Zen.usdInUahRate)
		RateIndicator.val(Zen.usdInUahRate)
})

DOM-to-data binding example:

Zen.formData = {}
Q('[data-form-field]')().on('input', function(){
	Zen.formData[this.dataset.formField] = this.value
})
//track form data changes
Zen(function(){
	if(Object.keys(Zen.formData).length) {
	// ... do something here
	}
})

These examples are pretty trivial but the capabilities are obviously much bigger.

!function(w) {
var J=JSON, trackInterval = 100, trackerFunctions = [], tfCount = 0, shadowZen = {},
objSerialize = function (obj) {
for(var keys = Object.keys(obj).sort(), res = {}, k;k=keys.shift();) res[k] = obj[k]
return J.stringify(res)
}, poller = function(k, shadowZenSerialized) {
if((shadowZenSerialized = objSerialize(shadowZen)) !== ''+zen) {
shadowZen = J.parse(shadowZenSerialized)
for(k=0;k<tfCount;k++) {
try {trackerFunctions[k].call(zen)}
catch(e) {}
}
}
setTimeout(poller, trackInterval)
}, zen = function(cb) {
trackerFunctions.push(cb)
if(!tfCount) poller()
tfCount++
return this
}
Object.defineProperties(zen, {
interval : {value: function(interval) {
trackInterval=interval
return this
}},
'import' : {value: function(obj, k) {
if(''+obj === obj)
try{obj=J.parse(obj)} catch(e) {}
if(obj)
for(k in obj)
if(obj.hasOwnProperty(k))
zen[k] = obj[k]
return this
}},
toString: {value: function(){
return objSerialize(zen)
}},
req: {value: function(key, url, data, xhr) {
xhr = new XMLHttpRequest
xhr.open(data ? 'POST' : 'GET', url, true)
xhr.onload = function(res) {
res = xhr.responseText
try {res = J.parse(res)} catch(e) {}
zen[key] = res
}
xhr.send(data)
}}
})
w.Zen = zen
}(this)
!function(p){var f=JSON,h=100,k=[],g=0,l={},m=function(a){for(var b=Object.keys(a).sort(),c={},d;d=b.shift();)c[d]=a[d];return f.stringify(c)},n=function(a,b){if((b=m(l))!==""+c)for(l=f.parse(b),a=0;a<g;a++)try{k[a].call(c)}catch(e){}setTimeout(n,h)},c=function(a){k.push(a);g||n();g++;return this};Object.defineProperties(c,{interval:{value:function(a){h=a;return this}},"import":{value:function(a,b){if(""+a===a)try{a=f.parse(a)}catch(e){}if(a)for(b in a)a.hasOwnProperty(b)&&(c[b]=a[b]);return this}},toString:{value:function(){return m(c)}},req:{value:function(a,b,e,d){d=new XMLHttpRequest;d.open(e?"POST":"GET",b,!0);d.onload=function(b){b=d.responseText;try{b=f.parse(b)}catch(e){}c[a]=b};d.send(e)}}});p.Zen=c}(this)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment