Wednesday, July 11, 2012

More Fun With Custom Events - state.js

My web apps these days are typically a single page, perhaps excepting a login page or some help content.  One page, composed from many files--during development, at least--does most of the work.  This design obviously began with the usual problems of lacking browser history control and bookmark-ability.  Looking at solutions others made, as you might expect, prompted me to reinvent another wheel.

Lo, state.js:

This enables browser history and bookmarks, by shoving simple key/value pairs into either the query string or the hash, as supported.  Keeping everything in the hash for all browsers would have been simpler, but then it would not survive a server-side login redirection process, undermining the ability to effectively bookmark a gated app.

state.js keeps track of the key/value pairs in memory, in order to notify listeners when those values are changed by the browser/user.  You can register a listener directly with state('foo', listenFn) or just watch for 'foo.state' events on the document.  This latter feature is part of the most interesting feature of state.js: it can be used entirely via custom events.  You do not need to ever directly reference the 'state' function in your app code or other js libs.  Granted, if you want soft dependency like this, it is easy to just have your config code wait for document ready and check if 'state' is available, but as state.js is such an event-oriented bit of code to begin with, i found it useful, amusing, and trivial to add an event-based API.

As an example, this:

state('foo', function(val, oldVal) {
    console.log('foo changed from ',oldVal,' to ',val);
});
state('foo', 'bar');// set foo to bar

is equivalent to this:

$(document).on('foo.state', function(val, oldVal) {
    console.log('foo changed from '+oldVal+' to '+val);
})
.trigger('state', ['foo','bar']);// set foo to bar

And here's the other useful features:

var all = state();// get a copy of current state
var foo = state('foo');// get current 'foo' state
$(document).trigger('back');// tell the browser to go back a page
$(document).trigger('forward'); // === history.forward()

As you can probably guess, this event-based API comes in quite handy when using trigger.js.  Now your page's controls can control state values and shift browser history:

<button trigger="nextPg state['pg',2]">Next</button>
<button trigger="back">Back</button>