Javascript Implementation of KVC
One of the greatest things about developing using the NeXT/Apple toolchain is the consistent use of something called key-value coding. It's the kind of thing that, once you buy into its philosophy, will suddenly make a whole slew of things easier for you in ways that you never thought of before. Every time I move to a new platform, be it Python or Javascript or Perl, I always find myself frustrated by its absence, and find myself jumping through all kinds of stupid hoops just to do things that would be dead-simple if key-value coding were available to me.
So here are a couple of Javascript implementations of KVC that you can glom onto your objects, or even glom onto everything in your system, and KVC will be available in all its glory (well, some of its glory... see below). At present it's designed to work with node.js, but will probably work as-is with other commonJS systems like Narwhal. I will work on getting it packaged up for use in a browser.
(Note: there is an almost-identical Perl implementation of key-value coding here.)
npm install keyvaluecoding
or check the project out from github if you want the bleeding edge.
There are a number of different ways to use key-value coding. For definitions of what "complex", "simple" and "additions" mean, see below.
To install complex KVC implementation system-wide: (this is the recommended way to use it, but if you don't like things messing with your universal object prototypes, you probably don't want this - however, this usage is most similar to the use of the NSKeyValueCoding category in OSX/iOS)
require("keyvaluecoding").install();
To install the simple KVC implementation system-wide:
require("keyvaluecoding").install({ simple: true });
To install the complex implementation with additions, system-wide:
require("keyvaluecoding").install({ additions: true });
To install any of these implementations on any specific object:
var thing = new Thing();
require("keyvaluecoding").install({ simple: true, target: thing });
If you're going to use that style, you can save the return value from require() and use it:
var thing = new Thing();
var kvc = require("keyvaluecoding");
...
kvc.install({ simple: true, target: thing });
or use jQuery style wrapping:
var thing = {};
thing = kvc(thing);
All implementations of KVC must support these methods:
valueForKey( <key> )
valueForKeyPath( <keypath> )
setValueForKey( <value>, <key> )
setValueForKeyPath( <value>, <keypath> )
At present there are only two implementations available, and they should be fine for your needs.
Once you have loading key-value coding and "installed" it into your runtime, any KVC-aware objects will now response to those methods. ( Note: the difference between a key-path and a key is that a key-path can be an arbitrarily long dot-path of keys ).
Here is an example session that should show how it works:
$ node
> require('keyvaluecoding').install()
undefined
> var foo = { bar: "This is foo.bar",
baz: { quux: "This is foo.baz.quux",
bonk: [ "This is foo.baz.bonk.0",
"and this foo.baz.bonk.1" ]
}
};
undefined
> foo
{ bar: 'This is foo.bar',
baz:
{ quux: 'This is foo.baz.quux',
bonk:
[ 'This is foo.baz.bonk.0',
'and this foo.baz.bonk.1' ] } }
> foo.valueForKey("bar")
'This is foo.bar'
> foo.valueForKeyPath("baz.quux")
'This is foo.baz.quux'
> foo.valueForKeyPath("baz.bonk.1")
'and this foo.baz.bonk.1'
>
If you use the (default) complex implementation, valueForKey( ) will also work for key paths, so you will never really need to use valueForKeyPath( ):
> foo.valueForKey("baz.bonk.0")
'This is foo.baz.bonk.0'
>
If a function is found rather than a property, it will be called in the context of the object it belongs to:
> foo.bing = function() {
return [ "This is foo.bing.0", "This is foo.bing.1" ]
}
[Function]
> foo
{ bar: 'This is foo.bar',
baz:
{ quux: 'This is foo.baz.quux',
bonk:
[ 'This is foo.baz.bonk.0',
'and this foo.baz.bonk.1' ] },
bing: [Function] }
> foo.valueForKey("bing.0")
'This is foo.bing.0'
>
The complex implementation allows nested key-paths, which are turned into arguments:
> foo.bong = function( bung ) { return bung.toUpperCase(); }
[Function]
> foo.bong("hey")
'HEY'
> foo.valueForKey("bong(baz.quux)")
'THIS IS FOO.BAZ.QUUX'
> var goo = { something: function() { return foo }, name: "I'm called goo" };
undefined
> goo
{ something: [Function],
name: 'I\'m called goo' }
> goo.valueForKey("something.bong(name)")
'I\'M CALLED GOO'
>
The corresponding set methods, setValueForKey and setValueForKeyPath will set the value on whatever object the key/keypath resolves to. If any part of the key or keypath returns null, the call will (at present) fail silently. NOTE: This is not the same behaviour as Apple's NSKeyValueCoding; it's a bit more like the Clojure "thread" operator (->>).
What are these "additions"? They're only available with the complex implementation of KVC, and they provide a number of "special" methods that can be used in keypaths:
For example:
$ node
> require('keyvaluecoding').install({ additions: true });
undefined
> var foo = { a: true, b: false, c: false };
undefined
> foo.valueForKey("and(a, b)")
false
> foo.valueForKey("or(a, b)")
true
> foo.valueForKey("or(b, c)")
false
Note that the arguments can be arbitrarily long key-paths.