Tuesday, March 29, 2011

Rewind: getters & setters for all IE with cross browser VBClass!

spoiler: if once again everybody knew except me, you guys should do something to be better indexed in Google ... while if this is totally new and cool, well, you are welcome :)



Sometimes I am stubborn, so stubborn, that even if it was me writing this post, and this one after, I have never given up about IE < 9 getters and setters ... "there must be a way", I have thought during last days, and ... yes, eventually I have found the way!

Test Driven Developed Solution

I have decided the possible behaviour, I have implemented all use cases, and I have successfully validated them against all browsers I could came up, with the exception of a single test, the first one, which fails in those browsers unable to freeze an object ... well, these browsers are disappearing thanks to their suggested, or automatic, updates, so everything is fine.
Here the unit test that should cover 100% of the used JavaScript and/or the used VBScript where available (only IE < 9 so please don't freak out yet!)

The Horrendous VBScript for IE

This wannabe Web programming language has been there, and hated, for ages ... but actually, if we learn all its gotchas we may end up thinking it is not that bad!
The main problem is to learn this "whatever it is language" and trust me: you don't wanna do that in this HTML5 era ... do you ?!

Not Only Get & Let, There Is A "Set" As Well!

Where good old dojo experimental Observable stopped, I didn't.
I kept investigating what the hack was going on behind the scene, forcing my fabulous IE9 to digest such Jurassic web programming language as VBScript is.
What I have discovered yesterday evening, is that when we set a property to a VBScript "unknown" object the Let definition is invoked only with primitives, where primitives are all those JavaScript variables which typeof is not equal to "object", with the exception of null value, and "function".
Once discovered this, the door to a proper implementation able to behave the same in all browsers became easy, and this is why I have introduced a new Class definition, the VBClass

What The Hell Is VBClass

VBClass is a global function able to create global factories, and these are the rules for a VBClass definition object:

  1. the definition of a VBClass IS static, which means once we have defined the structure of our instances, we cannot add, delete, or change arbitrary the number of properties

  2. since VBScript does not accept properties that start with an underscore, the convention to define a property "protected" must be different, as example using the underscore at the end as Closure Library does for whatever reason

  3. if a definition property is a function, this will be considered an immutable method of each instance created via this factory

  4. if a definition property has a value, its reference can be changed at any time and with any kind of object, function included, but in latter case it will not be possible to attach method runtime, but it will surely be possible to invoke the function property via call or apply, specifying the current object



A Full Specs VBClass Example


VBClass("FullSpecs", {
constructor: {
value: function (arg) {
// calls the method
this.method(arg);
}
},
method: {
value: function (arg) {
// invokes the setter
this.getSet = arg;
}
},
getSet: {
get: function () {
// returns the "protected"
return this.getSet_;
},
set: function (value) {
// assign the "protected"
this.getSet_ = value;
}
},
getSet_: {
// default "protected" value
value: null
}
});

var
genericObject = {},
test = new FullSpecs(genericObject)
;

test.getSet === genericObject; // true!


Pros And Cons Of VBClass

These are a few of pros about the precedent described VBClass limits/behavior:

  1. we have to think more about what we really need in our class, forgetting the completely dynamic JavaScript behaviour

  2. we are forced to follow a better convention for what we would like to define "protected properties"

  3. we can trust our defined methods, and we have to stick with them. This is a good/common approach if we consider a constructor.prototype runtime change a bad practice

  4. properties are properties, so there is nothing ambiguous about what is a method and what is a property: only the method can be invoked directly through the instance/variable/object, everything else is a property and there is no magic context injection there, neither for set functions


However, there are few cons to consider about this technique: unified behaviour through hosted VBScript objects means slower performances!
This means that VBClass created classes cannot be used for everything and there must be a valid reason to choose them in favour of normal JavaScript functions and their prototype nature.
A good reason could be, as example, the creation of those public object that would like to implement the coolness of a robust, cross platform, getters and setters implementation ... but be careful! If these objects are created hundred times during our application life-cycle, the performance impact could be massive and specially for older IE browsers.
Fortunately, with mobile browsers, IE 6/7 for Windows Phone 7 a part that I have not tested yet, the ideal scenario should fallback into the Object.create implementation, the one used in the main file, the only one needed, hopefully, as soon as users will update their browsers.

How To Use VBClass

Grab the source code or the minified version from my Google Code VBClass Project. Once all VBClass files are in the same folder, you can simply include the VBClass.loader.js on the top of your page and the rest of the magic is done.

<script
type="text/javascript"
src="http://vbclass.googlecode.com/svn/trunk/min/VBClass.loader.js"
></script>

Please copy VBClass locally to avoid round-trip and obtain better performances (also the trunk version in Google Code is not gzipped).

Have fun with VBClass ;)

Wednesday, March 16, 2011

Object.defineHybridProperty

Update Yes, I did it: getters and setters for IE < 9 and other browsers


After my early Hooorrayyyy! about compatible IE < 9 getters and setters, I have been experimenting a bit more on how to solve the JSObject to VBVariant and vice-versa assignment and the result was an horrendous monster loads of potential memory leaks and performances implications for the already slow bounce of browsers such IE8, 7, and 6.
Since limitations were also too many, as described in this even earlier attempt from dojo framework, I have realized that VBScript was simply a no-go, or better, probably the wrong answer to the question: how can I have cross-browser getters and setters?

The jQuery Hybrid Answer

Back in 2009, James Padolsey described the jQuery framework as a sort of getters/setters simulator API, comparing the semantic and beauty of non-standard Spidermonkey __defineGetter__ and __defineSetter__, against jQuery coding style, where many "methods" could be considered as getters or setters.

// get the innerHTML of the first node found through the selector
$("#selector").html();

// set the innerHTML of the first(?) node found through the selector
$("#selector").html("
");

As showed above, the jQuery().html method can be considered a friendly answer for a cross browser get/set implementation, and surely much more friendly than what antimatter15 proposed some time ago (Pssss! dude, I could not find your real name in the "About" section ...).

My "due jQuery success, why not!" Proposal

In ES5 everything is so natural and simple, Object.defineProperty and Object.defineProperties work like a charm and IE9 with all other browsers as well. We may decide to use good old __defineGetter/Setter__ as fallback for older Opera, Chrome, Safari, or Firefox, but not for IE since these methods are not supported at all.
However, even using these fallbacks, the descriptor object will result inconsistent because writable, enumerable, and configurable properties won't act as expected.
At this point I have decided to fallback into a "jQuery approach" solution that will behave exactly the same in all old and newer browsers, being still able to use an ES5 like descriptor that in a not that far away future won't even need to be changed at all, it will simply work.

A generic Person.prototype Descriptor



var personDescriptor = {
// requires council notification on change
name: {
get: function () {
return this._name;
},
set: function (name) {
// notify here the council about this change
this._name = name;
}
},
// this property is unfortunately immutable (via public access)
age: {
get: function () {
return this._age;
}
},
// simply a method that occurs once per year
birthday: {
value: function () {
this._age++;
}
}
};


Current ES5 Person "Class" Example



//* ES5 example
function Person(name, age) {
// a new Person in town
this._age = age || 0; // default, just born
this.name = name;
}

Object.defineProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// will throw an error
// me.age = 20;
alert([me.name, me.age]); // Andrea, 32
me.birthday();
alert(me.age); // 33
//*/


Current ES3 Person "Class" Example



//* ES3 example
Person = function Person(name, age) {

// a new Person in town
this._age = age || 0; // default, just born

// we still want to notify the council,
// no direct this._name set
this.name(name);

}

Object.defineHybridProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// nothing will happen
// me.age(20);
alert([me.name(), me.age()]); // Andrea, 32
me.birthday();
alert(me.age()); // 33

// wanna know what they are?
alert(me.name);
alert(me.age);
alert(me.birthday);
//*/

The source code of Object.defineHybridProperty, together with Object.defineHybridProperties, is here and as you can see it's a quite simple and compact piece of code.

Use Cases and DONTS

The function name should be explicit enough, and it's used to define hybrid properties.
Hybrid properties could be confused with methods ... and actually, all hybrid properties that have a get and/or set descriptor, become de-facto an object methods with current constrain: zero arguments to get, 1 single argument to set.
Use cases have been already described, a jQuery like API could use without problems the current approach, making the future ES5 only refactory less painful than whatever other get/set approach.
Indeed, once we know which descriptor is using getters/setters, all we have to do is to remember which property has been made hybrid, and change accordingly each invoke with arguments as assignment, and removing brackets from every other empty invoke.
Please Note I will update later the code in order to properly assign Object.prototype native names such toString so that IE browsers will consider them as well.
Done, it's updated and 409 bytes minified and gzipped, have fun :)