How do you override bindings up a JS constructor chain?

197 views Asked by At

I'm trying to define a base class in JavaScript that performs a lot of common functionality upon creation. Part of that functionality is to create a component and register callback handlers to that component.

The problem I'm having is how to override the function that's being used for that callback handler in child classes that extend my base class.

In concrete terms I have a BasicPage component that creates a Table component. The BasicPage also defines a default onRowClick function that gets registered with the newly created Table.

Now I want to create a PageWithSections component that extends BasicPage (via a call(..) statement) and overrides onRowClick. The problem is the registration of the click handler with the table happens within the constructor of the base class. At the time of that registration, onRowClick hasn't been overridden yet, so the reference is to the base classes version of onRowClick.

I've made a jsBin that illustrates the problem.

http://jsbin.com/uDEjuzeQ/9/edit

If you click on each box once, in order, I want the message box display to be:

No messages yet; row clicked; row clicked; BasicPage onRowClicked; row clicked; PageWithSections onRowClicked

What is the proper way to override a function up the constructor chain and bind the overridden function to something during construction of a base object?

UPDATE This question original referenced a prototype chain, but in truth the prototypes are not actually being used in this example.

The question was updated to reflect that. This ends up being more of a question about late binding.

3

There are 3 answers

1
Snekse On

My co-worker came up with one possible solution. As @cloudfeet said, it's not prototypal, but it works.

Basically he set the binding to a different instance function that in turn, called the _onRowClick function which at the time of execution would have been overridden.

http://jsbin.com/uDEjuzeQ/16/edit

0
HMR On

Here is some code that should work:

var BasicPage = function(){
  this.name="BasicPage";
  document.body.onclick=this._getClick(this);
};
BasicPage.prototype._getClick=function(me){
  return function(e){
    console.log("target is:,",e.target);
    console.log("this is:",me.name);
  }
};

var PageWithSections = function(){
  //initialise parent and it's instance members
  BasicPage.call(this);
  //override name
  this.name="PageWithSections";
};
PageWithSections.prototype=Object.create(BasicPage.prototype);
PageWithSections.prototype.constructor=PageWithSections;

var sect = new PageWithSections();
document.body.click();

The following code demonstrates how you could extend the onclick handler without copy and pasting the BasicPage code you already have:

var BasicPage = function(){
  this.name="BasicPage";
  document.body.onclick=this._getClick(this);
};
BasicPage.prototype._getClick=function(me){
  return function(e){
    console.log("re used code from BasicPage");
    console.log("target is:,",e.target);
    console.log("this is:",me.name);
  }
};

var PageWithSections = function(){
  //initialise parent and it's instance members
  BasicPage.call(this);
  //override name
  this.name="PageWithSections";
};
//set prototype chain
PageWithSections.prototype=Object.create(BasicPage.prototype);
PageWithSections.prototype.constructor=PageWithSections;
//extend _getClick
PageWithSections.prototype._getClick=function(me){
  var fn=BasicPage.prototype._getClick.call(me,me);
  return function(e){
    //do BasicPage click code
    fn(e);
    //extended code
    console.log("with a little extra from PageWithSections");
  };
};


var sect = new PageWithSections();
document.body.click();

More info on prototype and constructor functions here. The introduction should be very helpful and if you have time I would suggest reading all to get a good understanding of JS prototype.

1
cloudfeet On

The biggest issue I see is that your _onRowClicked (the callback you pass into the Table) is not actually defined in a prototype anywhere.

You are not actually using prototypical inheritance - you are defining the methods inside the constructor, and calling one constructor from another.

Try refactoring your code such that some of the default behaviour for BasicPage is defined in BasicPage.prototype (which is currently not referenced/altered at all). At that point, a solution that uses prototypical inheritance might suddenly become obvious. :)