After a good read though Javascript: The Good Parts , and the start of a rather large class that I'll be writing in my spare time, I thought of how I could setup private variables in a Mootools class.  YUI's Module pattern is an easy way to make some private variables, but it needed to fit into the Mootools Class pattern.  So I merged the two ideas, and created a Class Mutator, that lets you define a Privates object, and you get private variables!

Class.Mutators.Privates

I had found a Privates mutator already written by Nathan White, but my testing revealed some shortcomings.  You don't need to understand anything of what I've done to use it, but I'll show it for those who care:

Class.Mutators.Privates = function(self, privates) {
	delete self['Privates'];
	var oldInit = self.initialize;
	self.initialize = function() {
		var tempThis = $unlink(this);
		var instPriv = $unlink(privates);
		var instance = oldInit.apply(tempThis,arguments);
		for(var prop in tempThis) {
			if(instPriv.hasOwnProperty(prop)) {
				instPriv[prop] = tempThis[prop];
			} else  {
				this[prop] = tempThis[prop];
			}
		}
		var that = this;
		for(var key in tempThis) {		
			if($type(tempThis[key]) === 'function' && key !== 'initialize') {
				(function (fn) {
					var oldProp = that[key];					
					that[fn] = function() {
						var my = $merge(that, instPriv);
						var bound = oldProp.bind(my,arguments);
						var returns = bound();
						for(var prop in my) {
							if(instPriv.hasOwnProperty(prop) && $type(instPriv[prop] !== 'function')) {
								instPriv[prop] = my[prop];
							} else  {
								that[prop] = my[prop];
							}						
						}
						return returns;						
					};
				})(key);			
			}
		}
		return instance;
	}	
	return self;
};

I delete the Privates object out of the class, and redefine that initialize function, so that way all this magic happens for each instance, whereas most Mutators affect the class itself, and the first way I did it had the Privates object became static in nature, and all instances shared the private variables.

I make a copy of the class object, just as initialize does in the Native class, and pass that into the class' original initialize function. I then take that object and search for any properties that are supposed to be private, and update the private variables, and pass all other variables (meaning any new variables are public) to the public object. I also redefine every function of the class at initialize, and created var 's of the private object, and since everything is defined in the same function, the scope is never lost. After execution of any function, all properties that are private are copied into the private object. Each function is called with the object that contains this merged with the private object, so all the private variables are accessible as this.secret in the functions, like you'd expect in any other programming language.

An Example: Secret

Here's the class I wrote to do all my testing, and shows how everything works:

var Secret = new Class({
	Implements: [Options, Events],
	Privates: {
		secret: 'shhhh'
	},
	open: null,
	initialize: function(word) {		
		this.secret = word;
		this.open = 'not a secret';
	},
	getSecret: function() {
		return this.secret;
	},
	setSecret: function(newWord) {
		this.secret = newWord;
		this.notSecret = 'im a new prop in this';
	},
	getOpen: function() {
		return this.open;
	}
});

getSecret open is a public property of the Secret class. With a new instance, you'll notice you can access open publicly, but not secret.

var test = new Secret('this a secret test');
//test.open
//Reveals the open value
 
//test.secret
//Reveals undefined

Trying to set test.secret = 'a new secret' will set the secret property temporarily, but it doesn't override the private one, and when accessed in a function, the private one will instead write over the public one, since I check if the property is supposed to be private, and strip it from the public object.

Download and Notes

I doubt I did it the most effectively. After writing it, I tried refactoring it some by declaring a helper function that strips the private variables out of the public object, since I do that for loop twice. But doing so caused problems, and I wasn't up to the task of learning what the new problems were. By all means, if you see optimizations, I'd love to know them, I'll make an update, and I'll learn as well.

class-privates.zip

Update (11/18/09)

This only works prior to MooTools 1.2.3. In 1.2.3, a new implementation of Class.js was introduced, and I don't think this works the way I hoped in the newest versions. You can, though, now use a protect decorator on your functions.

Interested in Javascript or MooTools? Subscribe to my articles for free.

9 Comments

  1. Thank you for your work, as it inspired me much. Check out my solution for this problem: http://thomasdullnig.blogspot.com/2009/01/private-members-in-mootools-i-do-it-my.html

  2. Glad you found it useful. I did like how your Privates mutator function looks... cleaner than mine. Good job.

    Though, one thing I like about my version is that I do allow returning of this from methods. Since I've wrapped the method calls in a function that will clean out the private variables, you can still safely return this.

  3. Awesome! Thanks

  4. Glad you liked it!

    "Though, one thing I like about my version is that I do allow returning of this from methods. Since I've wrapped the method calls in a function that will clean out the private variables, you can still safely return this."

    Yeah, that's really a downside of my solution, maybe I'll find a way to handle this too.

  5. I've been mucking around with this myself, just for fun :) and this is whta i've come up with...

    Class.Mutators.Private = function(self, properties)
    {
    	var object = $merge($unlink(self), $unlink(properties));
    
    	for (var key in self)
    	{
    		if ($type(self[key]) === 'function' && key !== 'initialize' && key !== 'parent')
    		{
    			self[key] = self[key].bind(object);
    		}
    	}
    
    	return self;
    }
    

    Would this not be a simpler method of providing the same functionality? I'm still fairly new to mootools but I can't see a problem with this method.

  6. @Andy: Welcome to Mootools! And thanks for giving it a crack.

    I tested your mutator just to make sure; I find three issues that come up. Firstly, while you have hidden the private variables into the functions, you've also hidden the public variables.

    var a = new Secret();
    a.setNonSecret('hello');
    a.nonSecret //undefined
    

    Second, if you assign private variables during construction (initialize), which is incredibly common, since you don't bind it for that function, all the private variables just became public.

    And thirdly, I only noticed this when I made a second example to test something else. Multiple instances of the class share the same object. A new Secret already had a secret and nonSecret property set from my first instance.

    As Aaron has said, in Mootools 1.3, there's a few class changes that we can utilize to make a Privates mutator look and play nicer.

  7. Hi Sean,

    Thanks for the tips. I notices the issues you mentioned just after I posted the comment! After looking through and dissecting your method i've come up with an new attempt...

    Class.Mutators.Private = function(rootclass, properties)
    {
    	var init = rootclass.initialize;
    
    	rootclass.initialize = function()
    	{
    		var public  = $unlink(this);
    		var private = $unlink(properties);
    
    		var instance = init.apply(public, arguments);
    
    		for (var key in public)
    		{
    			if ($type(public[key]) === 'function' && key !== 'initialize' && key !== 'parent')
    			{
    				var self = this;
    
    				var execute = function(key)
    				{
    					var previous = self[key];
    
    					var method = function()
    					{
    						var object = $merge(self, private);
    
    						var returns = previous.bind(object, arguments)();
    
    						for (var property in object)
    						{
    							if (private.hasOwnProperty(property))
    							{
    								private[property] = object[property];
    							}
    							else
    							{
    								self[property] = object[property];
    							}
    						}
    
    						return returns;
    					};
    
    					self[key] = method;
    				};
    				execute(key);
    			}
    			else
    			{
    				this[key] = public[key];
    			}
    		}
    		return instance;
    	};
    
    	return rootclass;
    }
    

    It's a little more verbose than your example but has been invaluable in helping me understand the inner workings of mootools.

    Thanks for pointing me in the direction of Aaron's site, i've spent the last few days trawling through useful posts. I can't wait to see the new changes mootools 1.3 will bring, although I can't see much needed to improve an already excellent framework. Until then i'm going to enjoy discovering what else mootools has to offer. :)

  8. I am confused about the Mootools` class inner workings.
    Does

    var myClass = new Class({
        functionOne:function() {}
    })

    equals

    var myClass = function(){};
    myClass.prototype.functionOne = function(){};
    

    I mean will the functionOne be copied for each instance?

  9. @Tony: Yes, using MooTools' Class utility will assign all functions of the passed in object to the class prototype. It does some other useful stuff as well, like giving a feeling of classical inheritance, but still uses the (performance) benefits provided by prototypes.

Add a Comment

Search

Categories

Treats

See all »