Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

JavaScript Menu and Other Controls in OOP Style

4.00/5 (3 votes)
7 Apr 2008CPOL4 min read 1   207  
Writing JavaScript controls using principles of object-oriented programming

Introduction

This article is a modified version of the article from March 31 (which was removed) with library attached compatible with both Internet Explorer and Firefox browsers. It describes how JavaScript which doesn't support object-oriented features, makes up for all its limitations by using some simple conventions, rules and tricks. In addition, we offer you to download some basic JavaScript controls including Menu, TextBox, Checkbox, TableLayout which are written using exactly these methods. To see an example and get more advanced controls like Grid, ComboBox, Calendar, different input masks, please visit our site.

If you are familiar with the standard features - skip the following section.

Standard Object-Oriented JavaScript Features

Custom JavaScript Object (we will call it class for simplicity) can be defined as a function:

JavaScript
function Component(name, data)
{ 
	this._name = name;// public property
	this._data = data;// public property
	var someStaticData = null;// private static variable
	this.prototype.setName = _setName;// public member function
	this.prototype.getName = _getName;// public member function
	this.prototype.setData = _setData;// public member function
	this.prototype.getData = _getData;// public member function

	// function implementation
	function _setName(name)
	{
		this._name = name;
	}
	function _getName()
	{
		return this._name;
	}
	function _setData(data)
	{
		this._data = data;
	}
	function _getData()
	{
		return this._data;
	} 
	function someStaticFunction()
	{
		//some code here
	} 
} 

As it can be seen from the example above, four public methods are defined on the Component class. The instance of the class can be created and methods can be called on the instance as follows:

JavaScript
var component = new Component(); 
component.setName("SomeName"); 
component.setData("some string data"); 

Also, the _name and the _data fields are the properties of the class. Unfortunately all properties of JavaScript classes can be accessed from outside - they are all public:

JavaScript
component._name = "SomeName"; 

It is different with variables that are declared inside the class (see someStaticData). They cannot be accessed from outside, they are private. So what can they be used for? Thay are static and these variables are shared between all instances of the class. 
If method is not assigned to class's prototype (see above someStaticFunction), it is like a variable described above private static method - it should not contain in its implementation any class methods or properties.

Extended Object-Oriented Conventions

This chapter explains how you can using limited abilities of JavaScript to make it an Object-oriented language. Also we would show some programming tricks that optimize performance.

Initializers

Looking at earlier Component class, how can we make it reusable or implement first OO feature - inheritance? What if we define some function which we call Initializer that would take one parameter, class, and assign to it all required methods (we use suffix T for convenience):

JavaScript
function ComponentT(obj)
{ 
	obj.prototype.setName = _setName;// public member function
	obj.prototype.getName = _getName;// public member function
	obj.prototype.setData = _setData;// public member function
	obj.prototype.getData = _getData;// public member function

	// function implementation
	function _setName(name)
	{
		this._name = name;
	}
	function _getName()
	{
		return this._name;
	}
	function _setData(data)
	{
		this._data = data;
	}
	function _getData()
	{
		return this._data;
	} 
} 

Please notice that instead of this, in prototype assignment we use argument obj.
Then we remove all method declarations and implementation from the Component class:

JavaScript
function Component(name, data)
{ 
	this._name = name;// public property
	this._data = data;// public property
} 

With Initializer also comes big performance boost - try to create 100 class instances (put 10 methods inside the class constructor), then use Initializer for the same class, you will have the second approach work 10 times faster because prototypes are assigned only once instead of doing it on every object creation. Here we come to our first OOP feature - INHERITANCE.

Inheritance

Now let's put this line of code:

JavaScript
ComponentT(Component); 

What happened? When function is called, it assigns all methods inside ComponentT function to Component - Component inherits ComponentT.
This is not useful if you have just one Component, but if you need to extend its functionality in let's say Control class:

JavaScript
function Control(parent)
{ 
	this._parent = parent;// public property
} 

Then we would call it like this:

JavaScript
ComponentT(Control); 

Control class now inherits from Component. If you want to be able to inherit from a particular class, you should provide Initializer. So we do the same for Control class but now we remove the previous line of code and put it inside Initializer:

JavaScript
ControlT(Control); 
function ControlT(obj)
{ 
	ComponentT(obj);
	obj.prototype.setParent = _setParent;// public member function
	obj.prototype.getParent = _getParent;// public member function
	obj.prototype.paint = _paint;// public member function
	function _setParent(parent)
	{
		this._parent = parent;
	}
	function _getParent()
	{
		return this._parent;
	}
	function paint()
	{
		// some code
	}
} 

The picture will be more clear (if you don't get it yet) when we create TextBox which would extend Control which extends Component:

JavaScript
function TextBox(parent, text)
{ 
	this._text = text; 
} 
TextBoxT(TextBox); 
function TextBoxT(obj)
{ 
	ControlT(obj);
	obj.prototype.setText = _setText;// public member function
	obj.prototype.getText = _getText;// public member function
	obj.prototype.paint = _paint;// public member function
	function _setText(text)
	{
		this._text = text;
	}
	function _getText()
	{
		return this._text;
	} 
	function paint()
	{
		// some code
	}
}

That's not done yet, how about properties that are left uninherited.
This problem is solved by simple convention:

  • We remove all properties from inside class functions (Component, Control, Textbox).
  • Put one member function in Initializer of every class and call it initialize.
  • Inside implementation of this function, put corresponding properties.
  • Call this function from inside each constructor function.

The complete resulting code will be:

JavaScript
function Component(name, data)
{ 
	this.initialize(name, data); 
}
function Control(parent)
{ 
	this.initialize(parent); 
}
function TextBox(parent, text)
{ 
	this.initialize(parent, text); 
}
function ComponentT(obj)
{ 
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setName = _setName;// public member function
	obj.prototype.getName = _getName;// public member function
	obj.prototype.setData = _setData;// public member function
	obj.prototype.getData = _getData;// public member function
	// function implementation
	function _initialze(name)
	{
		this.setName(name);
	}// rest of implementation here
}
function ControlT(obj)
{ 
	ComponentT(obj);
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setParent = _setParent;// public member function
	obj.prototype.getParent = _getParent;// public member function
	obj.prototype.paint = _paint;// public member function
	function _initialze(parent)
	{
		Component.prototype.initialze.call(this); 
		this.setParent(parent);
		this.paint(); 
	}
	// rest of implementation here
}
function TextBoxT(obj)
{ 
	ControlT(obj);
	obj.prototype.initialze = _initialze;// public member function
	obj.prototype.setText = _setText;// public member function
	obj.prototype.getText = _getText;// public member function
	obj.prototype.paint = _paint;// public member function
	function _initialze(parent, text)
	{
		Control.prototype.initialze.call(this, parent); 
		this.setText(text);
	}// rest of implementation here
} 

Now everything is inherited. Please notice this nifty Control.prototype.initialze.call(this, parent); in TextBoxT's initialize method. If you don't know, it calls Control's implementation of initialize method buy passing itself as a reference telling function that this inside that function would be actually passed TextBox instance. It also passes parent as a first parameter. This allows to invoke the base class method. As you can see, we come to the next OOP feature - POLYMORPHISM.

Polymorphism

From the example above, notice that each of the classes in the hierarchy has its own initialize method, method is overwritten by subclass. So what happens if you have an instance of TextBox which extends Control created calling initialize method which in turn calls Control's initialize method which calls paint method?

JavaScript
var txtName = new TextBox(parent, "John Smith"); 

Both classes have paint method but correct method of TextBox will be called - this is called POLYMORPHISM.

Static Methods

You can declare static methods in JavaScript as follows:

  • It is declared as normal member function.
  • In its implementation, you cannot use classes non-static properties or functions.
  • It is called from outside using class constructor function, Control.prototype.foo();

Interfaces

Interfaces are created as classes with the difference that they don't have properties and in their function implementation, you throw an error - this forces developer to implement the method if interface is the class's hierarchy:

JavaScript
function BinderT(obj)
{ 
	IBinder(obj); 
	obj.prototype.setControl = _setControl;// public member function
	obj.prototype.getControl = _getControl;// public member function
} 
function IBinder(obj)
{ 
	obj.prototype.setControl = _setControl;// public member function
	obj.prototype.getControl = _getControl;// public member function
	function _setControl()
	{
	throw "Please implement setControl method.";
	}
	function _getControl()
	{
		throw "Please implement getControl method.";
	}
}

History

  • 7th April, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)