As @cbojar pointed out, just calling dialogBox.call(this, x, y, width, height);
will allow you to call the constructor on the parent class.
A few other things to point out:
- The naming convention in JavaScript for constructor functions is to capitalize the first letter in the name, e.g.
DialogBox
not dialogBox
. Native JavaScript constructor functions follow this same pattern (Object, Number, XMLHttpRequest, Array)
- Furthermore, most people define the "classes" in JavaScript using named functions, e.g.
function DialogBox
as opposed to var DialogBox = function
. The DialogBox.name
property will be "DialogBox" and this name will show up in error stack traces if something in the constructor blows up
- The
constructor
property of a prototype object should point to the constructor function for that "class". Be sure to set welcomeMessage.prototype.constructor = welcomeMessage
to make the representation more accurate.
- As @cbojar pointed out too, you can use
Object.create
now. If you must support older browsers, a pollyfill for this function is readily available by searching the web.
- Setting the dimensions should be refactored into it's own method
- The
STAGE
variable is a global variable used within the class, breaking encapsulation. Consider adding a stage
property to DialogBox
so you can stub out this object for unit testing
- Be sure to set default values for
x
, y
, width
and height
in the DialogBox
prototype. You want to avoid drawing NaN
values to your stage if at all possible and this is one way to go about it
Here would be my take on these two classes.
DialogBox
function DialogBox(x, y, width, height) {
this.setDimensions(x, y, width, height);
this.stage = DialogBox.stage;
}
DialogBox.stage = STAGE; // The default stage
DialogBox.prototype = {
x: 0,
y: 0,
width: 400,
height: 200,
constructor: DialogBox,
draw: function() {
this.stage.fillRect(this.x, this.y, this.width, this.height);
this.stage.strokeRect(this.x, this.y, this.width, this.height);
},
setDimensions: function(x, y, width, height) {
if (x != null)
this.x = Number(x);
if (y != null)
this.y = Number(y);
if (width != null)
this.width = Number(width);
if (height != null)
this.height = Number(height);
}
};
WelcomeMessage (Plain Old JavaScript Inheritance)
function WelcomeMessage(x, y, width, height, message) {
DialogBox.call(this, x, y, width, height);
this.message = message;
}
WelcomeMessage.prototype = Object.create(DialogBox.prototype);
WelcomeMessage.prototype.constructor = WelcomeMessage;
WelcomeMessage.prototype.addMessage = function() {
this.stage.strokeText(this.message, this.x + 10, this.y + 10, this.width - 10);
};
DialogBox (With its own "extend" method for easy inheritance)
function DialogBox(x, y, width, height) {
this.setDimensions(x, y, width, height);
this.stage = DialogBox.stage;
}
DialogBox.stage = STAGE; // The default stage
DialogBox.extend = function(Klass, proto) {
Klass.extend = DialogBox.extend;
Klass.prototype = Object.create(this.prototype);
Klass.constructor = Klass;
if (proto) {
for (var key in proto) {
Klass.prototype[key] = proto[key];
}
}
return Klass;
};
DialogBox.prototype = {
x: 0,
y: 0,
width: 400,
height: 200,
constructor: DialogBox,
draw: function() {
this.stage.fillRect(this.x, this.y, this.width, this.height);
this.stage.strokeRect(this.x, this.y, this.width, this.height);
},
setDimensions: function(x, y, width, height) {
if (x != null)
this.x = Number(x);
if (y != null)
this.y = Number(y);
if (width != null)
this.width = Number(width);
if (height != null)
this.height = Number(height);
}
};
WelcomeMessage (Using DialogBox.extend)
var WelcomeMessage = DialogBox.extend(
function WelcomeMessage(x, y, width, height, message) {
DialogBox.call(this, x, y, width, height);
this.message = message;
}, {
addMessage: function() {
this.stage.strokeText(this.message, this.x + 10, this.y + 10, this.width - 10);
}
}
);
Whichever inheritance scheme you choose, this is how you would use these two classes:
var box1 = new DialogBox(10, 58, 500, 300);
var box2 = new WelcomeMessage(35, 79, 650, 400, "Welcome!");
box1.draw();
box2.draw();
box2.addMessage();
Now we can tweak the WelcomeMessage
class to draw the message when draw
is called:
WelcomeMessage.prototype.draw = function() {
DialogBox.prototype.draw.call(this);
this.stage.strokeText(this.message, this.x + 10, this.y + 10, this.width - 10);
};
Or using the second inheritance scheme:
var WelcomeMessage = DialogBox.extend(
function WelcomeMessage(x, y, width, height, message) {
DialogBox.call(this, x, y, width, height);
this.message = message;
}, {
draw: function() {
DialogBox.prototype.draw.call(this);
this.stage.strokeText(this.message, this.x + 10, this.y + 10, this.width - 10);
}
}
);
Now box2.draw()
will show the message without requiring another function call.
Testing your classes
Now here's the kicker. In my refactored version, the STAGE
global is replaced by a static property: DialogBox.stage
. You can mock this object and unit test your classes. I'll use Jasmine tests as an example:
describe("DialogBox", function() {
var dialog, stage;
beforeEach(function() {
stage = {
fillRect: function() {},
strokeRect: function() {},
strokeText: function() {}
};
spyOn(stage, "fillRect");
spyOn(stage, "strokeRect");
spyOn(stage, "strokeText");
});
it("sets default dimensions", function() {
dialog = new DialogBox();
expect(dialog.x).toEqual(0);
expect(dialog.y).toEqual(0);
expect(dialog.width).toEqual(400);
expect(dialog.height).toEqual(200);
});
describe("draw", function() {
beforeEach(function() {
dialog = new Dialog(10, 20, 500, 300);
dialog.stage = stage;
});
it("draws the dialog on the stage", function() {
dialog.draw();
expect(stage.fillRect).toHaveBeenCalledWith(10, 20, 500, 300);
expect(stage.strokeRect).toHaveBeenCalledWith(10, 20, 500, 300);
});
});
});