Classify
Classify.js is a library that allows for cross platform and cross browser Object Oriented Javascript class definitions using classical inheritance and namespaces behind the prototype syntax in an easy to use interface function.
Install / Use
/learn @weikinhuang/ClassifyREADME
Classify.js 
Classify.js is a library that allows for cross platform and cross browser Object Oriented Javascript class definitions using classical inheritance and namespaces behind the prototype syntax in an easy to use interface function. Classify also provides "bound" properties that passes the calling context as the first argument, and "static" properties for static properties and methods on the object definition.
Classify is tested in IE 6+, Firefox 2+, Safari 3+, Chrome 3+, and Opera 10+, NodeJs.
Usage
Creating a class
var Pet = Classify({
type : "",
eaten : null,
init : function(type) { // constructor method
this.type = type;
// don't assign objects to the prototype, because it
// will be same reference in all instances
this.eaten = [];
},
eat : function(food) {
this.eaten.push(food);
}
});
Inheritance
var Dog = Classify(Pet, {
breed : "",
init : function(breed) {
this.$$parent("dog");
this.breed = breed;
}
});
Mixins/Multiple Inheritance
// Implementations will only be attached to the prototype
var feline_traits = {
is_asleep : false,
sleep : function() {
this.is_asleep = true;
},
wake : function() {
this.is_asleep = false;
}
};
var Cat = Classify(Pet, [ feline_traits ], {
breed : "",
init : function(breed) {
this.$$parent("cat");
this.breed = breed;
}
});
Static Properties/Methods
// Static properties can be defined with the "$$static$$" prefix
// or defined in bulk with $$static$$ : {prop1:1, prop2:2}
var Bird = Classify(Pet, {
breed : "",
init : function(breed) {
this.$$parent("bird");
this.breed = Bird.validateBreed(breed);
},
$$static$$BREEDS : {
parrot : "Parrot",
canary : "Canary"
},
$$static$$validateBreed : function(breed) {
return Bird.BREEDS[breed] || "Unknown";
}
});
Autobinding
// Auto bound properties can be defined with the "$$bind$$" prefix
// or defined in bulk with $$bind$$ : {prop1:function(){}, prop2:function(){}}
var Pig = Classify(Pet, {
init : function() {
this.$$parent("pig");
},
$$bind$$speak : function(event) {
// this can now be called within the class with this.speak()
// special property $$context added for the duration of this
// method to pass along the calling context, with "this" is
// still the instance
alert(this.$$context.href);
}
});
var p = new Pig();
// <a href="#oink" id="some-link">Click me!</a>
document.getElementById("some-link").addEventListener("click", p.speak);
Calling overridden parent methods
var Dog = Classify(Pet, {
breed : "",
init : function(breed) {
// "this.$$parent" is the parent of the calling method
this.$$parent("dog");
this.breed = breed;
},
eat : function() {
// the dog can only eat fish...
this.$$parent("fish");
},
eatBiscuit : function() {
// using "this.$$apply" can call any method in the parent prototype
// with an array of arguments similar to "Function.apply()"
// this will call Pet.prototype.eat with the argument biscuit
// shortcut for Pet.prototype.eat.apply(this, [ "biscuit" ]);
this.$$apply("eat", [ "biscuit" ]);
},
eatDogFood : function() {
// using "this.$$call" can call any method in the parent prototype
// with a set of arguments similar to "Function.call()"
// this will call Pet.prototype.eat with the argument biscuit
// shortcut for Pet.prototype.eat.call(this, "biscuit");
this.$$call("eat", "biscuit");
}
});
Namespaces
// creating/retrieving a namespace
var namespace = Classify("Life");
// OR
var namespace = Classify.Namespace.from("Life", {} /* any object can be a coerced into a namespace */);
// note that this cannot be accessed with 'Classify("Life");'
// if global access is needed use 'Classify.Namespace.from("Life", {}, true);'
method 1: through the namespace objects
// creating classes within a namespace
namespace.create("Reptile", {
species : "",
init : function(species) {
this.species = species;
}
});
// retrieving classes within a namespace
var Reptile = namespace.get("Reptile");
// extending classes within a namespace
namespace.create("Tortoise", "Reptile", {
init : function() {
this.$$parent("tortoise");
}
});
// retrieving classes within a namespace
var Tortoise = namespace.get("Tortoise");
namespace.create("Tortoise.DesertTortoise", "Tortoise", {
age : null,
init : function() {
this.$$parent();
this.age = 0;
}
});
// retrieving classes within a namespace
var DesertTortoise = namespace.get("Tortoise.DesertTortoise");
// instances
var pet = new DesertTortoise();
pet instanceof Tortoise;
pet instanceof Reptile;
pet.getNamespace() === namespace;
// removing a class within a namespace
namespace.destroy("Tortoise.DesertTortoise");
// checking a class within a namespace
namespace.exists("Tortoise.DesertTortoise");
method 2: through the Classify function
// creating classes within a namespace
Classify("Life", "Reptile", {
species : "",
init : function(species) {
this.species = species;
}
});
// OR using a "/" will denote namespace/classname
Classify("Life/Reptile", {
species : "",
init : function(species) {
this.species = species;
}
});
// retrieving classes within a namespace
var Reptile = Classify("Life", "Reptile");
// OR using a "/" will denote namespace/classname
var Reptile = Classify("Life/Reptile");
// extending classes within a namespace
// Classify("Life", "Tortoise", "Reptile", {
Classify("Life/Tortoise", "Reptile", {
init : function() {
this.$$parent("tortoise");
}
});
// retrieving classes within a namespace
var Tortoise = Classify("Life/Tortoise");
Classify("Life/Tortoise.DesertTortoise", "Tortoise", {
age : null,
init : function() {
this.$$parent();
this.age = 0;
}
});
// retrieving classes within a namespace
var DesertTortoise = Classify("Life/Tortoise.DesertTortoise");
// instances
var pet = new DesertTortoise();
pet instanceof Tortoise;
pet instanceof Reptile;
pet.getNamespace() === namespace;
Instantiating
var Reptile = Classify("Life/Reptile");
// instances
var pet = new Reptile("tortoise");
pet instanceof Reptile;
// instances
var pet = new Classify("Life/Reptile", [ "tortoise" ])
pet instanceof Reptile;
!(pet instanceof Classify);
Utility
(function(Classify) {
// here you can use the Classify object and remove the global reference to it
// this function is only available on browser environments
})(Classify.noConflict());
Environments
Classify is CommonJS compliant and can be used in the browser scope or the server scope.
In the browser:
<script src="path/to/classify.js" type="text/javascript"></script>
<!-- Classify() is now in the global context -->
In NodeJs environment:
npm install classifyjs
var Classify = require("classifyjs").Classify;
With an AMD loader like RequireJS:
require({
"paths" : {
"classify" : "path/to/classify"
}
}, [ "classify" ], function(Classify) {
console.log(Classify.version);
});
Building the Source
Classify uses the grunt build system. Building Classify requires node.js and a command line gzip program.
# Install grunt.
$ npm install -g grunt-cli bower
# Clone the Classify git repo.
$ git clone git://github.com/weikinhuang/Classify.git
$ cd Classify
# Install node modules.
$ npm install
$ bower install
# Run grunt.
$ grunt
Running the tests:
$ grunt test
For saucelabs users, test can be run from the test task with:
$ export SAUCE_USERNAME=[saucelabs username]
$ export SAUCE_ACCESS_KEY=[saucelabs access token]
To skip the saucelabs tests
$ grunt test:local
There are many other tasks that can be run through grunt. For a list of all tasks:
$ grunt --help
Changelog
v0.14.1
Adding `priority` property to mutators to allow for mutator processing order
v0.14.0
Ability to set internal references for namespaces created with `Namespace.from()`
v0.13.1
BREAKING CHANGE: Renaming more additional internal props for consistiency
Namespace.nsname => Namespace.$$nsname
Namespace.nsref => Namespace.$$nsref
v0.13.0
BREAKING CHANGE: the mutator prefix is now $$\w+$$
BREAKING CHANGE: underscores are no longer allowed in mutator names
BREAKING CHANGE: multiple mutators can be specified in the mutator prefix separated by an _
BREAKING CHANGE: the mutator prefix is always removed regardless if there is a mutator attached or not
greedy mutators can be added to always be run onPropAdd and onPropRemove
// prefix change
{ __mutator_prop : 1 } => { $$mutator$$ : 1 }
// multiple mutators
{ $$mutate1_mutate2$$ : 1 }
// greedy mutators
addMutator({ greedy : true })
// prefix removed when attached to an object
$$abc$$prop : 1 => prop : 1
v0.12.0
BREAKING CHANGE: Class.toString now returns the init method body
BREAKING CHANGE: Renaming internal properties and certain magic properties.
BREAKING CHANGE: Change behavior of the bind mutator to make context a property instead of a argument.
// calling parent method
this.parent => this.$$parent
this.invoke => this.$$apply
adding this.$$call
// internal variables
Class.superclass => Class.$$superclass
Class.subclass => C
