Jumpstart Lab Curriculum

Object-Oriented Javascript

So far, our charting library has been very functionally organized. We have top level functions on the main area of the page, and they all reference each other in a disorganized way. In this chapter, we’re going to revisit our core functions and organize them into object-oriented classes.

Prototypal Classes

Javascript’s class system is "Prototypal" because when classes are created, they have a "Prototype." Let’s look at an example:

1
2
3
4
5
6
7
8
9
10
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function () {
  alert("Hello, I am " + this.name + ".");
};

var joe = new Person('Joe');
joe.sayHello();

First, we are setting up the Person variable to store a function. This function is known as the constructor or initializer because it is called when a new person is created. It takes a name argument which it simply sets on itself to use later.

Next, we define the prototype attribute on the function. By defining a prototype, when new is called, the new object will have a copy of the prototype attached to it. Meaning that any objects on the prototype are available on the object itself.

Typically, methods are stored in the prototype. All instances of a class share the same prototype. This means that our methods can exist one place instead of being copied for each and every object we instantiate.

We can then call joe.sayHello(), which will run the sayHello function on the prototype in the context of joe, so that this.name exists from the constructor step.

Add a few more methods and some more constructor arguments to the person class. What happens if you make one of the prototype attributes something other than a function?

The Prototypal Inheritance Chain

By default, each class comes with an empty object as its prototype. In the previous examples, we referenced this prototype in Person.prototype. Any instantiated object can serve as the prototype for a class.

Let’s create a Programmer class that inherits from our Person class:

1
2
3
4
5
6
7
8
9
function Programmer(name, language) {
  this.name = name;
  this.language = language;
}

Programmer.prototype = new Person();

var jane = new Programmer('Jane', 'JavaScript');
jane.sayHello();

We didn’t have to declare a sayHello method on our Programmer class. When we called sayHello on jane, JavaScript checked jane to see if the object had a sayHello method. It didn’t, so JavaScript, checked its prototype, which is an instance of the Person class. This object didn’t have sayHello method either, we JavaScript checked its prototype, which is where sayHello is ultimately defined.

Inheriting from an instance of an object might seem a bit strange at first, but it allows us to extend the prototype without modifying the class it inherits from.

1
2
3
4
5
Programmer.prototype.sayLanguage = function () {
  alert('I like to program in ' + this.language + '.');
};

jane.sayLanguage();

In the example above, we didn’t actually modify the Person class, because Programmer inherits from an instance of the Person class. This means, we can modify it and extend the prototype, without changing the Person class itself.

The Chart Class

The first class we’ll make is a Chart class that will support some of our common functions used across all our charts:

1
2
3
4
5
6
7
8
9
10
function Chart(context) {
  this.context = context;
}

Chart.prototype = {
  rectangle: function(color, x, y, width, height) {
    this.context.fillStyle = color;
    this.context.fillRect(x, y, width, height);
  }
}

Now that we have the chart class, we can use it:

1
2
3
var context = document.getElementById('drawing').getContext('2d');
var chart = new Chart(context);
chart.rectangle('red', 0, 0, 300, 200);

On your own, add the text method to the Chart class:

The Bar Chart Class

Next, we can create a BarChart class to encapsulate the Bar Chart drawing logic. First, we’ll setup our constructor:

1
2
3
4
function BarChart(context, data) {
  this.context = context;
  this.data = data;
}

A Bar Chart will need an additional option when constructed: the data it needs to draw. Other than that, it just needs a context like our Chart class. Now let’s write the prototype:

1
2
3
4
5
6
7
8
9
10
11
BarChart.prototype = {
  rectangle: Chart.prototype.rectangle,
  bar: function(x, y) {
    this.rectangle("rgb(64, 64, 128)", 0, x * 12, y * 10, 10);
  },
  draw: function() {
    this.data.forEach(function (datum, index) {
      this.bar(index, datum);
    }.bind(this));
  }
}

First off, we’re copying (actually, just referencing) the Chart class’s rectangle function. This way we can bring in functionality from the Chart class to use in our BarChart class. Next, we’ve got a bar function that calls our rectangle function just like it did in the previous lesson, except we’re calling it on this, since our function is now encapsulated in the class.

Lastly, we have a draw function that does the data loop for us. The only difference is calling this.bar and this.data. That means our script can simply be:

1
2
3
var context = document.getElementById('drawing').getContext('2d');
var barChart = new BarChart(context, [4, 8, 12, 1, 7]);
barChart.draw();

Experiment: Pie Chart and Line Chart

On your own, convert your pie chart methods and line chart methods into classes. Once you have them set up, try some of the following challenges:

  1. Add a color parameter to the bar chart constructor so you can set the color when you call new
    1. Next, if a color parameter is not passed in, default to black. (HINT: what is the value of color when it is not sent in to the constructor as a parameter?)
  2. Add a starting x and y to the constructor for your charts to tell it where to start drawing that chart on the screen. Then draw one of each chart on the canvas side by side.
  3. Add a setData function to one of your charts that overwrites its data attribute and re-draws itself.
Feedback

Have Feedback?

Did you find an error? Something confusing? We'd love your help:

Thanks!