Intro
OOP is a great concept, it simplifies the programmer’s life, especially in the field of interface development. Nearly all modern web-interfaces use JS in some way - for field highlighting, validation, help etc. It’s OK when there are such simple things.
And now imagine, that we have a set of dialogue windows (HTML+CSS) for adding a comment, rating, sending to the friend. Each of these boxes has a number of functions - check if user is logged in(if not - show login form), send AJAX request, process response, show error/success messages, position the box on the page etc.
The problem
First idea I had about them is to use simple inheritance, create Box class, derive CommentBox, RatingBox and other classes, but it turned out, that jQuery (I used it there, because system is built using Yii framework, which uses jQuery for client-side coding) doesn’t support extending classes like, for example, ExtJS or YUI. Yes, it has $.extend() function, but it just copies the object, not actually extends class. Why it is not the same? Let’s look here:
-
//namespace
-
var Popups = {};
-
//class constructor
-
Popups.Box = function()
-
{
-
//initialization here
-
}
-
Popups.Box.prototype = {
-
container:$(‘<div class="container"></div>’),
-
content:null,
-
create:function()
-
{
-
//dom elements generation here
-
},
-
showHide:function()
-
{
-
//some logic here
-
}
-
//other methods and properties go here
-
}
Normally (in ExtJS, for example) when writing:
-
Popups.CommentBox = $.extend(true,{}, Popups.Box, {
-
create:function()
-
{
-
alert(‘overriden!’);
-
}
-
})
I expect, that if I do:
-
var c = new CommentBox();
-
c.create();
I should get an alert window. However, this is not true for jQuery. It just copies everything and we get some messed things. So I gave this up and followed another pattern.
The solution
Box object became container for all boxes. I took care of messaging, determining if user is logged, and all other routines. It also serves as a factory, because all clicking icons go to the showHide method and it has to determine, which box to show and create this inner object.
And this inner object takes care of the box-specific logic. If it needs some common info, for example, the ID of the product it is called for, it calls the Box method (see LowCoupling GRASP principle here :)). So in the Box I have:
-
getBoxType:function()
-
{
-
//determine the box we’re dealing with by the link class
-
var tg = $(this.event.target);
-
if (tg.hasClass(‘f’))
-
return ‘favourite’;
-
else if (tg.hasClass(‘t’))
-
return ‘friend’;
-
else if (tg.hasClass(‘ra’))
-
return ‘rate’;
-
else if (tg.hasClass(‘c’))
-
return ‘comment’;
-
else if (tg.hasClass(‘r’))
-
return ‘rss’;
-
else if (tg.hasClass(‘add’))
-
return ‘addTag’;
-
else if (tg.hasClass(‘b’))
-
return ‘bookmark’;
-
return ”;
-
},
-
getInnerObj:function()
-
{
-
if (!us.logged)//if user is not logged, we show him the login box instead of anything else
-
{
-
return new Popups.LoginObject(this);
-
}
-
var cls;
-
this.type = this.getBoxType();
-
switch (this.type)
-
{
-
case ‘favourite’:
-
cls = this.objects[this.type] = new Popups.FavouriteObject(this);
-
break;
-
case ‘friend’:
-
cls = this.objects[this.type] = new Popups.FriendObject(this);
-
break;
-
case ‘rate’:
-
cls = this.objects[this.type] = new Popups.RateObject(this);
-
break;
-
case ‘comment’:
-
cls = this.objects[this.type] = new Popups.CommentObject(this);
-
break;
-
case ‘rss’:
-
cls = this.objects[this.type] = new Popups.RssObject(this);
-
break;
-
case ‘bookmark’:
-
cls = this.objects[this.type] = new Popups.BookmarkObject(this);
-
break;
-
default:
-
cls = this.objects[this.type] = null;
-
}
-
return cls;
-
},
-
show:function()
-
{
-
//some stuff
-
var obj = this.getInnerObj();
-
if (typeof obj == ‘object’ && obj != null)
-
{
-
this.content.append(obj.getContents());
-
}
-
},
So instead of inheritance I implemented aggregation (Box aggregates CommmentObject, Favourite object etc).
Everything goes well until you want to to attach some method of current object as a handler to some event. For example:
-
Popups.Box.prototype = {
-
//properties here
-
productID:0,
-
-
create:function()
-
{
-
this.form.submit(this.onSubmit);
-
productID = this.parentBox.getItemID();
-
},
-
onSubmit:function()
-
{
-
alert(this.productID);
-
}
-
//other methods follow
-
}
Right? Wrong! Because when submit event occurs, jQuery will call the onSubmit method, but this will refer to the form, not to the Box object! However, it is logical to put processing of the form into the same object. In ExtJS and YUI you can pass the scope parameter for every callback function, so you can control the “this” variable in the callback call. jQuery doesn’t allow this. I used the following workaround:
-
Popups.Box.prototype = {
-
//properties here
-
productID:0,
-
-
create:function()
-
{
-
var ctx = this;
-
this.form.submit(function(event){
-
ctx.onSubmit(event);
-
});
-
productID = this.parentBox.getItemID();
-
},
-
onSubmit:function(event)
-
{
-
alert(this.productID);
-
}
-
//other methods follow
-
}
If you need the object, that triggered an event, just pass “this” there:
-
create:function()
-
{
-
var ctx = this;
-
this.form.submit(function(event){
-
ctx.onSubmit(this, event);
-
});
-
productID = this.parentBox.getItemID();
-
},
-
onSubmit:function(form, event)
-
-
alert(this.productID);
-
}
Having these 2 tricks you can use OOP approach to designing reusable interface components in a handy modern way.
Back to inheritance
Since I discovered the problem with inheritance, I want to develop a plugin, that fixes this, that will provide nice class inheritance model to take jQuery a step ahead. I was going to digg the ExtJS’s Ext.extend() code and find out how this is done correctly, but while preparing an article, I found this material, which is actually a guide for creation of such plugin: http://phrogz.net/JS/Classes/OOPinJS2.html. Unfortunately, I don’t have time for this now, but I plan to do this in the nearest future. If someone does this earlier, please let me know
Links
- ExtJS - a component, interface-oriented JS framework
- YUI - Yahoo library for the user interface. ExtJS was derived from it long ago. They are similar in extending and event processing (the “scope” I mentioned initially appeared in YUI)
- jQuery - a lightweight VERY popular JS framework with an easily extensible structure, but lacks OOP techniques!
No related posts.










Zecc says:
You’re doing it wrong. It’s easy to be confused my JavaScript’s somewhat complex object model.
Instead of
Popups.CommentBox = $.extend(true,{}, Popups.Box, { /* … */ });
you should do
Popups.CommentBox.prototype = $.extend(true, {}, new Popups.Box(), { /* … */ });
You’ll even get
box = new Popups.CommentBox();
if( box instance Popup.CommentBox && box instanceof Popups.Box ) alert(’You betcha!’);
[Reply]
January 31, 2010, 7:32 PM