statey
note, this was abstracted out of human-model and is not super well tested as a standalone yet
So, if it looks like HumanModel, it's because it's largely pulled from there.
The plan is to make this the base object for HumanModel that doesn't include any assumptions about REST or how you're going to use it. Thus making it useful for anytime you want something to model state, that events changes and lets you define and listen to derived properties.
In pursuit of the ultimate observable JS object.
So much of building an application is managing state. Your app needs a single unadulterated source of truth. But in order to fully de-couple it from everything that cares about it, it needs to be observable.
Typically that's done by allowing you to register handlers for when things change.
In our case it looks like this:
// Require the libvar Statey = ; // Create a constructor to represent the state we want to storevar Person = Statey; // Create an instance of our objectvar person = name: 'henrik'; // watch itperson; // set the value and the callback will firepersonisDancing = true;
So what?! That's boring.
Agreed. Though, there is some more subtle awesomeness in being able to observe changes that are set with a simple assigment: person.isDancing = true
as opposed to person.set('isDancing', true)
(either works, btw), but that's nothing groundbreaking.
So, what else? Well, as it turns out, a huge amount of code that you write in a project is really in describing and tracking relationships between variables.
So, what if our observable layer did that for us too?
Say you wanted to describe a draggable element on a page so you wanted it to follow a set of a rules. You want it to only be considered to have been dragged if it's total delta is > 10 pixels.
var DraggedElementModel = Statey; var element = x: 0 y: 0; // now we can just watch for changes to "dragged"element;
</sarcasm>
You didn't invent derived properties, pal. True, derived properties aren't a new idea. But, being able to clearly declare and derive watchable properties from a model is super useful and in our case, they're just accessed without calling a method. For example, using the draggable example above, the derived property is just element.dragged
.
Handling relationships between objects/models with derived properties
Say you've got an observable that you're using to model data from a RESTful API. Say that you've got a /users
endpoint and when fetching a user, the user data includes a groupID that links them to another collection of groups that we've already fetched and created models for. From our user model we want to be able to easily access the group model. So, when passed to a template we can just access related group information.
Cached, derived properties are perfect for handling this relationship:
var UserModel = Statey; var user = name: 'henrik' groupId: '2341'; // now we can get the actual group model like so:usergroupModel; // As a bonus, it's even evented so you can listen for changes to the groupModel property.user;
Cached, derived properties are da shiznit
So, say you have a more "expensive" computation for model. Say you're parsing a long string for URLs and turning them into HTML and then wanting to reference that later. Again, this is built in.
By default, derived properties are cached.
// assume this linkifies stringsvar linkify = ; var MySmartDescriptionModel = Statey; var myDescription = description: "Some text with a link. http://twitter.com/henrikjoreteg"; // Now i can just reference this as many times as I want but it // will never run it through the expensive function again. myDescriptionlinkified;
With the model above, the descrition will only be run through that linkifier method once, unless of course the description changes.
Derived properties are intelligently triggered
Just because an underlying property has changed, doesn't mean the derived property has.
Cached derived properties will only trigger a change
if the resulting calculated value has changed.
This is super useful if you've bound a derived property to a DOM property. This ensures that you won't ever touch the DOM unless the resulting value is actually different. Avoiding unecessary DOM changes is a huge boon for performance.
This is also important for cases where you're dealing with fast changing attributes.
Say you're drawing a realtime graph of tweets from the Twitter firehose, instead of binding your graph to increment with each tweet, if you know your graph only ticks with every thousand tweets you can easily create a property to watch.
var MyGraphDataModel = Statey; // then just watch the propertyvar data = numberOfTweets: 555; // start adding 'emvar { datanumber += 1;}; data;
Derived properties don't have to be cached.
Say you want to calculate a value whenever it's accessed. Sure, you can create a non-cached derived property.
If you say cache: false
then it will fire a change
event anytime any of the deps
changes and it will be re-calculated each time its accessed.
statey can be extended as many times as you want
Each statey object you define will have and extend
method on the constructor.
That means you can extend as much as you want and the definitions will get merged.
var Person = Statey; var AwesomePerson = Person; // Now awesome person will have both awesomeness and name propertiesvar awesome = name: 'henrik' awesomeness: 8; // and it will have the methods in the originalawesome; // returns 'hi, henrik' // *BUT* it doesn't maintain the prototype chain// so instanceof checks will fail up the chain // so this is trueawesome instanceof AwesomePerson; // true; // but this is falseawesome instanceof Person; // false
Changelog
- 0.0.2 - improved doc
- 0.0.1 - initial publish
Credits
License
MIT