How do you override bindings up a JS constructor chain? - javascript

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.

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. :)

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.

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

Related

JavaScript - Correct way to remove event listener added by my class [duplicate]

I am trying to remove an eventListener but it looks like I miss something.
Why is the following code not working, it doesn't remove the event listener from the button.
I also tried binding this to pass the scope, but that didn't work either
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
Prototype methods as event handlers are a bit problematic, specifically when you need both, the this value bound to the instance, and the reference to the actual event handler function.
By default, the event queue calls the handler in the context of the element the event was bound to. It's easy to change the context, but that provides you to create a new function, which then is used as the event handler, and that function is not the method in the prototype anymore.
If you want to keep the compact class structure, one way is to define the event handler methods as own properties of the instance, they simply can't be inherited. The simplest way would be to define the methods as arrow functions in the constructor.
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
The arrow function keeps the reference to the lexical environment it was defined in, and the event queue can't override the context. Now this in the handler function is correctly bound to the instance, and this.eventHandler refers to the function, which was attached to the event.
A slightly less memoryconsuming option would be to use bind when creating the own property, like this:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
Here bind creates a new function object, which then calls the method in the prototype, the actual code of the method is not duplicated. This is loosely similar if you wrote:
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
It's notable, that when defining an own property with the same name an underlying prototype property has, the prototype property is not overridden, it's only shadowed in the instance, and multiple instances of the class will still work as intended.
Another option is to create your own "event model", which creates a wrapper function (like in the very last code example above) for all events, and stores the reference to that function. The wrapper calls the actual handler with call, which can bind the wanted this value to the event handler. The stored function references are used to remove events. Building such a model is not extremely complex, but it provides a bit knowledge of how the this binding and native event model work.
The OP's code does not work for two reasons.
in one case the prototypal eventHandler misses the correct this context.
for a second case of running this.eventHandler.bind(this) one creates a new (handler) function with no saved reference to it. Thus with removeEventHandler one never refers to the correct event handler.
Possible solution ...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Another possible approach was the usage of an arrow-function based, thus instance-specific, event-handler. Arrow-functions do not support an explicit this binding. They always refer to the context where they are implemented in.
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Nevertheless, both approaches show, that the prototypal implementation of a reference-specific event-handler is not the path one should follow.
For the scenario provided by the OP I would pick the 1st solution for it provides code reuse by the locally implemented handleTestClickEvent. It also comes with a smaller footprint regarding the instance specific this.eventHandler which for the former gets created from handleTestClickEvent.bind(this) whereas the 2nd solution provides a full handler implementation to every single instance.

Is there any way of knowing if a method is being run from within a class?

I'm interested in being able to determine whether or not a class method is being executed within the class itself or from outside, and having different functionality based that.
What's a way of determining whether or not a class method is being executed within the class itself?
This is a quick and dirty example of what I'm interested in:
class Example {
internal = false
meow () {
console.log(this.internal);
return 'meow'
}
makeSound () {
return this.meow.bind({...this, internal: true})();
}
}
const example = new Example();
console.log(example.meow())
console.log(example.makeSound())
Here, Example#meow runs two times, one from within Example#makeSound and once on it's own. I would like a way of knowing from within Example#meow when the method has been executed from within the class, versus from outside the class, as in example.meow().
One way to do this, is with binding all internal calls to methods, if I add a property internal true, to this bound copy of this, then I can check from within Example#meow where the call is coming from.
This is only a proof of concept, but it's not great to try and clone this, and seems to be a real pain.

How do I reference the containing class from within a static method when "this" is already taken by the calling object?

Using ES6/ES2015 and webpack, I am trying to wrap my head around the little monster that is the keyword this.
I have a class Edit containing a static method change(event) and within that method I am trying to make a recursive call to the method itself (depending on a given variable).
In most cases I could just call this.change(event) but here the keyword this is already occupied by the jquery object that was calling the function instead of the containing class.
The easiest solution would be to just call Edit.change(event) instead, but there must be a cleaner solution. Every programming language I have encountered so far has had some reference to its containing class built in.
I promise I have checked the docs and other threads on stackoverflow, but no one I found seems to address this particular problem.
// main.js
'use strict';
const $ = require('jquery');
const Edit = require('./Edit');
$(document).ready(() => {
let thingsToAddToData = {
whatToDo: // defined here depending on context
someVariable: //defined here depending on context
};
$('table :input').change(thingsToAddToData, Edit.change);
}
and here the Edit class is defined
// Edit.js
class Edit {
static change(event) {
if(event.data.whatToDo === 'do_nothing'){
return false;
}
if(event.data.whatToDo === 'do_this_important_thing'){
// here some important stuff is done
return true;
}
if(event.data.someVariable === 'special_case'){
event.data.whatToDo = 'do_this_important_thing'
// THIS IS THE LINE THAT GIVES ME HEADACHES
return this.change(event);
}
// here some default stuff is done
}
}
module.exports = Edit;
The easiest solution would be to just call Edit.change(event) instead, but there must be a cleaner solution
No, this is indeed what you need to use to always refer to the Edit class. There's nothing messy with it, just use it.
You could also use this.change(event) if you weren't using the method as an event handler. Make sure to call it as a method:
$('table :input').change(thingsToAddToData, Edit.change.bind(Edit));
// or
$('table :input').change(thingsToAddToData, e => Edit.change(e));
Either of the answers by #Bergi, should work (using Function.prototype.bind or () => {}). However I think your problem is more structural. Since Edit.change is an event handler it doesn't make sense to call it directly, since it is supposed to be fired through events.
I would suggest firing the event again with some parameter changes (http://api.jquery.com/trigger/):
replace Edit.change(event); with this.trigger(event);
That way there is no need for calling the handler directly, and you don't need to change the this context, thus keeping the code more transparent.
Static methods operate on the class instead of instances of the class, they are called on the class. There are two ways to call static methods:
<ClassName>.methodName()
or
<class-instance>.constructor.methodName()
In static methods, the this keyword references the class. You can call a static method from another static method within the same class with this.

Prototype method not being called

I'm creating a custom object on click and then trying to access it's prototype's methods.
$(function(){
$('.addtask').on("click", function(){
var new_task = new Task();
$('.deletetask').on("click", function(){
new_task.deleteTask();
});
function Task(){
this.html="<li>Add Description<span class='deletetask'></span></li>";
}
Task.prototype = {
constructor: Task,
deleteTask: function(){
this.remove()
},
}
});
});
Now when i click on class deleteTask it says:
TypeError: new_task.deleteTask is not a function
new_task.deleteTask();
How can I access the prototype method deleteTask() ??
You're setting the prototype of Task after you've created your new Task. You're not changing the prototype of your already created instances by doing so, as the prototype is referenced at the instance creation.
Move the Task.prototype = { assignation before the creation of your new Task.
If you really want to add a function to all instances after they're created, change the prototype of the constructor instead of replacing it :
Task.prototype.deleteTask = function(){...
You should move the definition of Task and its prototype methods before the initial .on call. This will incidentally fix the problem identified by #dystroy whereby you're trying to set the prototype of Task after creating an instance of it.
As written the Task function is recreated every time the click handler is invoked, and therefore the Task objects created on each click will be instances of different classes, which makes using the prototype moot. Prototype methods are an effective way of sharing methods amongst multiple instances, and achieving inheritance, but you're using neither.
Alternatively, leave the Task function where it is but abandon using prototype methods, e.g.:
function Task() {
this.html = "<li>Add Description<span class='deletetask'></span></li>";
this.removeTask = function() {
this.remove();
}
}
(NB: this.remove is also undefined at this point?)

Javascript Object Inheritence

I'm creating a control for Google maps v2. While creating my control I've found a design challenge and want to find an appropriate solution. Here's the goods.
A custom Google control inherits from GControl;
myControl.prototype = new GControl();
Next I need to overload the initializer so here it is.
myControl.prototype.initilize = function (map) {
//do some work and return stuff
};
Now within my custom controls initlize function I create a couple elements which, using the GEvent class, I subscribe to various events. To make my callback functions managable, I included them into the controls prototype.
myControl.prototype.onEvent = function(e){
//do some work;
//modify the members of the current myControl instance
};
Within my callback function "onEvent" I want to modify members within my control. What is the best way to access my control from the function? The keyword "this" cannot be used because that is a reference to the element that was clicked, in my case a div. And I can't access the members through the prototype because I need a specific instance of the object. The only viable solution I've considered is to create my control globally in one of my scripts. Is this the best method?
The easiest thing that I can think, it to define your onEvent method within your constructor, there you will have quick access to the current object instance, and you will not have to modify your public API:
function MyControl () {
var instance = this; // store a reference to 'this'
this.onEvent = function (e) {
// use the instance variable
instance.otherMethod();
};
}
Note that in this approach, the onEvent property will exist physically in your object instances (obj.hasOwnProperty('onEvent') = true).
Edit: You can simply use the GEvent.bind function:
GEvent.bind(map, "click", myObj, myObj.onEvent);
The above bind method will enforce the context, so the this keyword inside myObj.onEvent will point to the myObj object instance when the event is triggered, it will work with your code without problems.
I'm not familiar with how you subscribe to events using GEvent, so I'll make that part up. Do this:
myControl.prototype.onEvent = function(e, instance) {
// do some work
// modify members of 'instance'
};
function wrap(handler, instance) {
return function(e) {
handler(e, instance);
}
}
GEvent.Register('Event', wrap(instance.onEvent, instance));

Categories

Resources