JavaScript is a very simple but powerful language, nearly everything can be change dynamically at runtime. This allows for example to adapt exisiting code for example in libraries at runtime.
a = new Object(); // Properties can be added at runtime a.foo = 'World'; // but adding behavior is no difference... a.bar = function() { return 'Hello ' + this.foo} a.bar() // -> Hello World
This dynamic nature of the language can be used in many ways for adapting existing behavior. One general technique is wrapping existing functionality
a.bar = a.bar.wrap(function(oldFunc) {return oldFunc()+ "!"}) a.bar() // Hello World!
Which is a nice technique for trying out new behavior in a instance-specifically scoped way. But when we continue to develop the new behavior we get into problems....
a.bar = a.bar.wrap(function(oldFunc) { return oldFunc().toUpperCase() + "!" }) a.bar() // HELLO WORLD!!
The new behavior has become the old behavior and if we don't replace we are stuck with it! There is one "!" to much. The problem is that the behavior is now bound the the objects. The solution is to not directly bind the behavior variations to the instance, but to define them in a layer and activate that layer dynamically.
cop.create('ScreamLayer').refineObject(a, { bar: function() { return cop.proceed().toUpperCase() + "!" } }) cop.enableLayer(ScreamLayer) a.bar(); // HELLO WORLD! cop.disableLayer(ScreamLayer) a.bar() // Hello World
Since, now the bevhavior adaption can be scoped, the new and old behavior can coexist in one system, and be active depending on the context. We found this a very valuable feature for developing Lively Kernel from inside a Lively Kernel hosted system. We can safely experiment with core functionality without having to worry to much about shouting ourselfs in the foot. ;-)
Layers in ContextJS are simple objects. Typically they have a name and are stored in a global variable with the same name, and they inherit from the class "Layer".
cop.create('MyNewLayer')
would be the same as
MyNewLayer = new Layer('MyNewLayer');
The former eliminates the error prone redunant name declaration.
Since, Lively Kernel (the system we developed ContextJS for) is a mostly class based system we have special syntax in ContextJS for layering classes. Classes in JavaScript a only a pattern of using the prototypical inheritance and are no language feature.
In Lively Kernel a simple class definition looks the following:
Object.subclass("Person", { initialize: function(name) { this.name = name }, say: function(something) { alert(this.name + " says: " + something) }, toString: function() { return "Person(" + this.name + ")" } }); joe = new Person("Joe") joe.say("Hello")
Now we can refine the behavor of the function "say" in ScreemLayer.
cop.create('ScreamLayer').refineClass(Person, { say: function(something) { cop.proceed(something.toUpperCase() + "!") } }) enableLayer(ScreamLayer) joe.say("Hello") disableLayer(ScreamLayer) joe.say("Hello")