A topic that keeps me busy for a while now is how can I achieve private members in a Mootools classes without rewriting the whole Class.js?
Today morning I stumpled upon
Sean McArthurs and
Dean Whites ideas to the topic, but I thought there must be a simpler way to achieve this.
My idea was to have two different class-objects, one is public and doesn't know anything about the private members, the second object has the first object as its prototype extended with all private members. The second object gets bind to all methods of the class. So you can use all private members within each method of the class, while you can't use it outside of the class. Sounds complicated? Maybe, but I don't know how to explain it simpler. Ask me in German and I think I can give you a simpler explanation.
Let's start with the class mutator:
Class.Mutators.Privates = function(self, privates){
//From "Javascript - The Good Parts" (Douglas Crockford)
var F = function () {};
F.prototype = self;
var privSelf = new F();
$extend(privSelf, privates);
for(var prop in self){
if($type(self[prop]) === 'function' && (prop !== 'parent' && prop !== 'parentOf')){
var parent = (self[prop]._parent_ || null);
self[prop] = self[prop].bind(privSelf);
if(parent !== null){
self[prop]._parent_ = parent;
}
}
}
delete self['Privates'];
for (var privProp in privates) {
delete self['Privates'][privProp];
}
privSelf.publicSelf = self;
};
That's all.
I will explain it step by step:
var F = function () {};
F.prototype = self;
var privSelf = new F();
As I explained above, making a new object as 'private object', that knows all private members, and has the 'public object' as prototype. So you can extend the class or 'public object' and your 'private object' can use the new members too, without extra work.
$extend(privSelf, privates);
Now extend the 'private object' with the private members.
for(var prop in self){
if($type(self[prop]) === 'function' && (prop !== 'parent' && prop !== 'parentOf')){
var parent = (self[prop]._parent_ || null);
self[prop] = self[prop].bind(privSelf);
if(parent !== null){
self[prop]._parent_ = parent;
}
}
}
Next loop through all properties of the 'public object' searching for methods we want to bind the 'private object' to. If we found one and it's not the mootools related methods 'parent' and 'parentOf' (they are needed for class extending), we cache its parent-function, bind the 'private object' to the public method and attach the parent function if the old function had one. We have to do this, or otherwise you can't use this.parent, when you extending a class.
delete self['Privates'];
for (var privProp in privates) {
delete self['Privates'][privProp];
}
So far so good, now do some cleanup.
privSelf.publicSelf = self;
This one is special. You need it to do chaining. As you can't return 'this' in your methods, remember you bound the 'private object' to all public methods, just returning 'this', will reveal all your private members. So I decide to attach the 'public object' to the 'private object'. If you want to return the class object for chaining, you can do something like 'return this.publicSelf'.
My Test-Case:
window.addEvent('domready', function(){
var Test0 = new Class({
method0: function(){
console.log('method0', this);
}
});
var Test1 = new Class({
Privates: {
'secret': 'secret',
'secretMethod': function(){
console.log('secretMethod', this);
}
},
Implements: [Options, Events],
Extends: Test0,
initialize: function(){
this.secret = 'test1-secret';
console.log('init', this);
},
publicMethod1: function(){
console.log('publicMethod1', this);
this.secretMethod();
this.instanceMethod();
this.implementedMethod();
},
setSecret: function(val){
this.secret = val;
},
getSecret: function(){
return this.secret;
}
});
var Test2 = new Class({
Extends: Test1,
Privates: {
'anotherSecret': 'secret2',
'secretObject': {
'prop1': 'save',
'prop2': 'and',
'prop3': 'sound'
}
},
initialize: function(){
this.anotherSecret = 'test2-secret';
},
publicMethod2: function(){
console.log('publicMethod2', this);
//Chaining without revealing your private members
return this.publicSelf;
},
publicMethod1: function(){
console.log('Test2.publicMethod1', this);
},
setSecret: function(val){
this.anotherSecret = val;
},
getSecret: function(){
return this.anotherSecret;
}
});
var t = new Test1();
console.log('t', t);
t.method0();
t.instanceMethod = function(){
console.log('instanceMethod', this);
};
Test1.implement({
'implementedMethod': function(){
console.log('implementedMethod', this);
}
});
t.implementedMethod();
t.instanceMethod();
var tt = new Test1();
t.setSecret('t');
tt.setSecret('tt');
console.log('t', t.getSecret());
console.log('tt', tt.getSecret(), 't', t.getSecret());
var t2 = new Test2();
console.log('t2', t2);
var secretThis = t2.publicMethod2();
console.log('secretThis', secretThis);
t2.publicMethod1();
t2.setSecret('t2 secret');
console.log('t2', t2.getSecret());
var t0 = new Test0();
console.log('t0', t0);
t0.method0();
});
I know the test looks rather complicated and unstructered, oh and you need
firebug, or
firebug lite or something, as I use 'console.log' all over the place. But you can see very well when you get the 'public object' and when the 'private object'.
Tell me what you think about it? Is it useful, or not? Are there any bugs in it?