27 March 2012
A general understanding of coding in JavaScript as well as object-oriented design principles in programming will help you make the most of this article.
Additional required other products
Node.js
Intermediate
One of the difficulties in learning JavaScript for anyone coming from a classical object-oriented programming (OOP) language background is adjusting to how objects are defined and extended. Obviously, JavaScript has neither a traditional concept of classes nor classical inheritance. Many JavaScript frameworks try to overcome this deficiency by providing a syntax for defining classes and methods for handling inheritance and composition. CoffeeScript and similar languages that compile to JavaScript also provide a more traditional syntax for handling this as well. Clearly, despite assertions that trying to force JavaScript into a classical object model isn't really a good idea, the issue keeps coming up.
I recently came across Minion, a solution created by Taka Kojima. It is a lightweight JavaScript library for defining classes with support for classical style object-oriented inheritance and composition, among other features. While it currently requires Node.js to compile and can work out of the box with Node, you can use Minion in the browser. In this tutorial, I revisit the simple example and model that I created for a JavaScript prototype inheritance tutorial that I posted previously on my blog. I recommend that you read this post, in which I describe how I built simple Portal turret objects, because it will help you better understand the example used in this article. This article covers how I rebuilt the example with Minion and how this approach compares with a straight JavaScript (that is, framework-less) solution.
To use Minion in your browser-based application, you first need to download it via the Node Package Manager ( npm ) and compile it via Node.js. Now that npm comes with Node and Node has easy-to-use installers even for Windows, this is pretty simple to accomplish.
npm to download Minion:npm install minion -gminion src minion.jsThis will generate the JavaScript file, minion.js (a non-minified version), that you can copy to your scripts folder and include in your web application. See the Minion getting started guide for additional details, including how to generate a minified version. Minion also gives you the option to minify the JavaScript containing your classes when finished, though I do not cover that in this tutorial.
The most basic class syntax in Minion simply has a property, an initialization method, and another basic method. The following class (Weapon.js) doesn't do much but it will serve as a basis for demonstrating how Minion handles inheritance later on.
minion.define("portal", {
Weapon : minion.extend("minion.Class", {
fireType : "auto",
init: function(){
},
getFireType: function(){
return this.fireType;
}
})
});
Classes are created using the minion.define() function in which you define the namespace (in this case it is "portal" ) and then the class. Every class in Minion must, at some level, extend the base class, which is named minion.Class. Inside the minion.extend() call, you see a fairly typical way of writing pseudo-classes in JavaScript. Much like traditional OO languages, Minion expects that every class is written in its own file and provides mechanisms to let you organize these in folders much the way you might see classes organized in ActionScript or Java.
As I said, at some level, every class needs to inherit from minion.Class, but your classes can inherit from each other provided this requirement is fulfilled somewhere up the chain. For example, my MachineGun.js and HeatSeekingMissile.js classes, which provide the weaponry of my Portal turrets, both inherit from Weapon.js above. Here is my MachineGun.js class:
minion.define("portal", {
MachineGun : minion.extend("portal.Weapon", {
weaponType : "machine gun",
init: function(){
this.__super();
this.getFireType();
this.weaponType += " (" + this.fireType + ")";
},
getFireType: function(){
this.fireType = this.__super();
}
})
});
Note that not much changed from the prior class example except that this class extends portal.Weapon. To access the super class, you call the __super() method from within your class methods. In init() this will invoke the init() method of the super class; in getFireType() it will invoke getFireType() of the super class and so on. You can pass arguments with your __super() call as well. I didn't find a means of directly accessing the super properties other than via a method call nor a way to call a super method other than the one of the same name. Still there are some fairly easy and obvious workarounds to this by building accessor methods for the values you require.
Minion can also handle classes that have a or have many instances of another class. For example, my Turret.js class comprises a weapon, which by default is a machine gun (MachineGun.js). Here is the code for this class:
minion.define("portal", {
require : [
"portal.MachineGun"
],
Turret : minion.extend("minion.Class", {
laserEye : true,
isEnemyInSight : false,
isFiring : false,
isKnockedOver : false,
motionDetectedArr : ["Target acquired","There you are","I see you","Preparing to dispense product","Activated"],
knockedOverArr : ["Critical error","Shutting down","I don't hate you","Hey, hey, hey","Malfunctioning"],
ignoredArr : ["Are you still there"],
machineGun : null,
init: function(){
this.machineGun = new this.__imports.MachineGun();
},
arm: function() {
return this.machineGun.weaponType + " armed";
},
fire : function(){
this.isFiring = true;
console.log("firing");
},
motionDetected : function(){
this.isFiring = false;
this.isEnemyInSight = false;
this.isKnockedOver = true;
return this.knockedOverArr[Math.floor(Math.random() * this.knockedOverArr.length)];
},
knockedOver : function() {
this.isFiring = false;
this.isEnemyInSight = false;
this.isKnockedOver = true;
return this.knockedOverArr[Math.floor(Math.random() * this.knockedOverArr.length)];
},
ignored : function(){
if (this.isKnockedOver) {
return "I am knocked over";
}
this.isEnemyInSight = false;
this.isFiring = false;
return this.ignoredArr[Math.floor(Math.random() * this.ignoredArr.length)];
}
})
});
While the code for this class is rather long, it really doesn't stray much from the prior examples and is, in fact, quite similar to the code from my framework-less sample. Because Turret is composed of MachineGun, I needed to add the snippet near the top that tells Minion to require the portal.MachineGun class. You can specify any number of required classes here in the same manner. Once this is done, I can access a special scope named __imports that contains the imported classes. You can see that I assign this.machineGun as a new MachineGun() in this way within the init() function:
this.machineGun = new this.__imports.MachineGun();Now that my classes are complete, I want to use them in the same sample page as my framework-less example. Most everything about the code remains the same except for loading in the classes via Minion. Here is the HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Minion Sample</title>
<script src="js/minion.js"></script>
<script>
var rTxt, turret, heatSeekingMissile;
function load() {
rTxt = document.getElementById('responseTxt');
minion.configure({
classPath : "com"
});
minion.require(
["portal.Turret", "portal.HeatSeekingMissile"],
function(Turret, HeatSeekingMissile){
turret = new Turret();
rTxt.value = turret.arm();
heatSeekingMissile = new HeatSeekingMissile();
}
);
}
function motionDetected() {
rTxt.value = turret.motionDetected();
}
function knockedOver() {
rTxt.value = turret.knockedOver();
}
function ignored() {
rTxt.value = turret.ignored();
}
function modTurret() {
turret.heatSeekingMissile = heatSeekingMissile;
turret.arm = function() {
return this.heatSeekingMissile.weaponType + " and " + this.machineGun.weaponType + " armed";
};
rTxt.value = turret.arm();
}
</script>
</head>
<body onload="load()">
Response: <input name="responseTxt" id="responseTxt" type="text" size="60" /><br />
<input type="button" name="motionBtn" value="Motion Detected" onclick="motionDetected()"/>&nbsp;
<input type="button" name="knockedBtn" value="Knocked Over" onclick="knockedOver()"/>&nbsp;
<input type="button" name="ignoredBtn" value="Ignored" onclick="ignored()" />&nbsp;
<input type="button" name="missilesBtn" value="Heat Seeking Missiles!"onclick="modTurret()" />
</body>
</html>
Take a look at the load() function. It defines for Minion the classpath for my JavaScript files, which is just the relative location of the base file folder containing all my class scripts. In this case, I've placed them in a folder named com. Next, it informs Minion to require any classes that my application needs, which in this case is simply the Turret and HeatSeekingMissile classes (the latter for reasons I'll cover momentarily).
Now, despite being a more classical object-oriented way of defining classes, inheritance, and composition, Minion does not change the dynamic nature of JavaScript. In my prior example, I illustrated this by modding a heat seeking missile onto my Turret. As you can see in the modTurret() function above, I can still dynamically add heatSeekingMissile objects to my class defined via Minion.
In my testing I ran into an interesting issue. Specifically, the loaded JavaScript was cached by the browser (Chrome) and not reflecting my changes. I regularly needed to clear the cache after changes to the JavaScript classes for them to be reflected. This can be corrected, however, by appending a timestamp to file calls via the configure() method. Simply modify the configure method shown above as follows:
minion.configure({
classPath : "com",
fileSuffix : Date.now()
});
This article has not touched on all the features of Minion, just the basics you'd need to build your classes. The framework provides a means of defining static properties and methods, creating singletons and static classes, and publishing and subscribing to events and notifications triggered within your model. You can get more details about these in the getting started guide. While Minion is relatively new, it seems pretty robust as a start and the author informs me that it is in use in some large projects, including one for Toyota. Still, the documentation was limited as I developed these classes, so even this simple example did take some trial and error. I do think it is worth checking out if you are looking for a way to structure your JavaScript application in a more classical OO manner, though I am staying out of the debate on the value of this.
If you would like to run the example from this tutorial, you can find it on my site here. The source code is available with the sample files for this article.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.