My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
Class  
Class creation and management for jQuery
Updated Feb 4, 2010 by micah.snyder@gmail.com

The Class Class

At Digg we use jQuery extensively, but it doesn't offer a much in the way of Object-Oriented JavaScript. Existing OOJS libraries weren't a good fit -- often trying to shoehorn traditional OOP patterns in where they don't quite fit -- so we rolled our own.

Enjoy!

Part 1: Creating and Manipulating Classes and Namespaces

Hello World

Simplest usage, creates a dynamic class in the current scope

var MyClass = Class.create();

var mc = new MyClass();

Static / Singleton

And now, a static class. Anytime the last argument passed to Class.create is boolean, it'll serve as the static switch (defaults to false).

var MyClass = Class.create(true);

MyClass.namespace('foo');

Adding the Constructor and Methods

Ok, so we have classes now, but their constructors and prototypes are empty. We could use $.extend, but why not make it a bit tidier? You can pass any number of objects to Class.create and they'll be tacked onto your class' prototype (dynamic) or the class itself (static). The 'init' method serves as each Class' constructor.

var method = {
    someFunc: function(){}
}

var MyClass = Class.create({
    init: function(){
        console.log('You instantiated a Class!');
    },
    myFunc: function(){},
    myProp: 'foo'
}, method);

var foo = new MyClass();

foo.someFunc();

Namespaces

Alright, so we have classes now, and they have methods. What's next? Namespaces. Class.namespace is a badass object manipulation method. If you pass it a string, it'll turn that string into a static class inside the current one.

var MyClass = Class.create(true);

//this...
MyClass.namespace('bar');

//...is the same as this
$.extend(MyClass, {
  bar: Class.create(true);
});

Deep Namespaces

But wait, there's more. You can pass in a dot-notated String for more fun. Note: existing properties will not be overwritten. If MyClass.foo exists, MyClass.namespace('foo.bar') will keep foo as is and copy bar into it, though you should really use MyClass.foo.namespace('bar') at that point.

var MyClass = Class.create(true);

//this...
MyClass.namespace('bar.baz.quux');

//...is the same as this
$.extend(MyClass, {
  bar: Class.create(true);
});

$.extend(MyClass.bar, {
  baz: Class.create(true);
});

$.extend(MyClass.bar.baz, {
  quux: Class.create(true);
});

Existing Global Namespaces

Annnd more: If a namespace already exists as an object in the global scope, it'll be copied into your class. This is handy if you have an existing object or class sitting around and you want to integrate it into your obsessive/compulsive organization scheme.

var MyClass = Class.create(true);

var stuff = Class.create({
    things: function(){}
}, true);

MyClass.namespace('bar.stuff');

//MyClass.bar.stuff.things.constructor == Function

Namespace / Method Combo Object

But wait, ''there's more'': Pass in an object and it'll run through and copy its properties that are Objects or Functions into your Class.

var MyClass = Class.create(true);

var stuff = Class.create({
    things: function(){}
}, true);

MyClass.namespace({
    stuff: {
        things: function(){}
    }
});

//MyClass.stuff.things.constructor == Function

Multiple Namespaces With an Array

Want to add multiple namespaces at the same level? Pass in an array of strings or objects:

var MyClass = Class.create(true);

MyClass.namespace([
    'foo.bar',

    {
        baz: {
            doStuff: function(){}
        }
    },

    'quux'
]);

Dynamic Classes in Namespaces

Phew. Ok then. So we have Classes, they have methods and namespaces (which are, by default, static classes). Next up: Dynamic classes inside namespaces. For this we'll use the version of Class.create that gets prototyped onto all classes (so MyClass.create, not Class.create). It differs from the main one in that you pass it a name for the inner Class before all of the regular arguments, like so:

var MyClass = Class.create(true);


MyClass.create('MyOtherClass', {
    myOtherFunc: function(){}
});

var foo = new MyClass.MyOtherClass();

foo.myOtherFunc();

The Terrible Secret of Space

Let's put it all together for one last demo:

var D = Class.create(true);

D.namespace('estroy.humans');

D.estroy.humans.create('Now', {
    rawr: function() {
        console.log('BOOM! SMASH! POW!');
    }
});

var foo = new D.estroy.humans.Now();

foo.rawr();

Part 2: Extending Classes

First, let's talk about what happens when you extend a class.

var Foo = Class.create({
    fooz: function() {
        alert("Foo's fooz was called");
    }
});

var Bar = Class.create(Foo.prototype, {
    fooz: function() {
        alert("Bar's fooz was called");
    }
});

You could say that we just extended Foo with Bar, and for convenience's sake we'll refer to this process as "extending" a class, but that's not quite what's really going on. What we've done here is we've created the class Bar with a copy of Foo's prototype, and overwritten Foo.prototype.fooz.

Remember, JavaScript doesn't use classical inheritance. We have no formal classes, nor subs, nor supers. What we have is Objects, their prototypes and a perverse need to wrangle them into a pattern that resembles normal OOP. For that reason, when Class sees a duplicate property whose constructor is Function, it will copy that method into an object called 'supers' before overwriting it.

The effect of that copy is this:

var Foo = Class.create({
    fooz: function() {
        alert("Foo's fooz was called");
    }
});

var Bar = Class.create(Foo.prototype, {
    fooz: function() {
        this.sup();
        alert("Bar's fooz was called");
    }
});

The call above to "this.sup()" (The word 'super' wasn't used because it's reserved in JS 2.0) invokes the copy of Foo.prototype.fooz that was stored in Bar.prototype.supers. Calling Bar.prototype.fooz() will now trigger both Foo and Bar's alerts. What we have here, in essence, is a very simple connection between a sub-class and its super.

It comes with a significant caveat though: It only works one level above the current class, each time you extend a class and overwrite one of its methods, only the last duplicate method is stored in the "supers" object.

Consider the following:

var Foo = Class.create({
    fooz: function() {
        console.log('fooz of Foo');
    }
});

var Bar = Class.create(Foo.prototype, {
    fooz: function() {
        this.sup();
        console.log('fooz of Bar');
    }
});

var Baz = Class.create(Bar.prototype, {
    fooz: function() {
        this.sup();
        console.log('fooz of Baz');
    }
});

Baz.prototype.supers.fooz now contains code that invokes "this.sup", which is a reference to Baz.prototype.supers.fooz. Enjoy your beachball!

In the future we may add support for inheriting as far down as you'd care to go, but for now this single level adds as much classical inheritance as we'd care to see in JavaScript.

Comment by team.force@sbrmarketing.com, Apr 15, 2010

Excellent

Comment by yoze...@gmail.com, Sep 6, 2010

Is this tested with Safari? Because I can't get inheritance to work with Safari

Comment by panni...@gmail.com, Mar 13, 2011

This adds unlimited inheritance with sup():

diff -r a09d8bcfd134 site-media/js/Class-0.0.2.js
--- a/site-media/js/Class-0.0.2.js	Sun Mar 13 01:28:47 2011 +0100
+++ b/site-media/js/Class-0.0.2.js	Sun Mar 13 23:12:34 2011 +0100
@@ -36,6 +36,11 @@
  * @requires Array.indexOf -- If you support older browsers, make sure you prototype this in
  */
 
+/*
+ * updated for unlimited inheritance with sup(), Hannes Tismer, 2011
+ */
+ 
+ 
 /**
  * @class Class A singleton that handles static and dynamic classes, as well as namespaces
  */
@@ -65,6 +70,7 @@
             
             //a container to hold one level of overwritten methods
             supers: {},
+            superCount: 0,
             
             //a constructor
             init: function() {},
@@ -161,8 +167,11 @@
             //call the super of a method
             sup: function() {
                 try {
-                    var caller = this.sup.caller.name;
-                    this.supers[caller].apply(this, arguments);
+                    var caller = this.sup.caller.name, self=this, selfargs = arguments;
+                    //this.supers[caller].apply(this, arguments);
+                    $.each(this.supers[caller], function(k, sooper) {
+                        sooper.apply(self, selfargs);
+                    });
                 } catch(noSuper) {
                     return false;
                 }
@@ -192,12 +201,15 @@
                 for(i in this) {
                     /* if a property is a function (other than our built-in helpers) and it already exists
                     in the class, save it as a super. note that this only saves the last occurrence */
-                    if(extendee[i] && extendee[i].constructor == Function && ['namespace','create','sup'].indexOf(i) == -1) {
+                    if(extendee[i] && extendee[i].constructor == Function && $.inArray(i, ['namespace','create','sup']) == -1) {
                         //since Function.name is almost never set for us, do it manually
                         this[i].name = extendee[i].name = i;
                         
                         //throw the existing function into this.supers before it's overwritten
-                        extendee.supers[i] = extendee[i];
+                        /*extendee.supers[i] = extendee[i];*/
+                        extendee.superCount+=1;
+                        if (typeof(extendee.supers[extendee[i].name]) == "undefined") extendee.supers[extendee[i].name] = [];
+                        extendee.supers[extendee[i].name].push(extendee[i]);
                     }
                     
                     //extend the current property into our class
Comment by toursdul...@gmail.com, Dec 21, 2011

I don't think they're useless, I just don't think, at this point in time, that they're critical. At this point in time, I think that New Orleans needs engineers.

I also know that Engineering salaries are much higher, but faculty are expected to bring in their salary in grants every year. You're supposed to "pay your own way" via grants. Not necessarily that way in all liberal arts departments (of course, there aren't as many big $ grants available in liberal arts areas of study). http://www.intour.com.vn/dich-vu/lam-visa-di-cong-tac-thu-tuc-lam-visa-di-cong-tac-dich-vu-lam-visa-di-cong-tac-lam-visa-di-cong-tac-gia-re.html http://www.intour.com.vn/dich-vu/dich-vu-lam-the-apec-thu-tuc-lam-the-apec-lam-the-apec-gia-re-the-apec.html http://www.intour.com.vn/tour-du-lich-phap.html


Sign in to add a comment
Powered by Google Project Hosting