I am learning how to work with Gia (for small web projects) and I cannot find out how to pass an event object from one component to an event handler of another component over Gia's eventbus.
Here's two basic components, communicating over the eventbus:
class navigation extends Component {
constructor(element) {
super(element);
//
// Define "Sub-components"
this.ref = {
navLinks: [],
};
}
mount() {
//
// Listen for clicks on nav.-links
for (let i = 0; i < this.ref.navLinks.length; i++) {
const link = this.ref.navLinks[i];
link.addEventListener("click", this.handleNavLinkClick.bind(this));
}
}
handleNavLinkClick(e) {
//
// Emit event
let clickedLink = e.target;
if (clickedLink.classList.contains("callHeader")) {
eventbus.emit("callingSubpageHeader");
}
}
}
class subpageHeader extends Component {
mount() {
//
// Listen for call from eventbus
eventbus.on(
"callingSubpageHeader",
this.handleEventBusCall_callHeader.bind(this)
);
}
//
// Eventbus handler(s)
handleEventBusCall_callHeader() {
console.log("The subpage-header was called.");
}
}
The emitting of the event and the subsequent call of the handler inside the second component works just fine. But I would like to pass additional information from the first to the second component when the handler is called. The Gia documentation mentions that the emit method of the eventbus can pass an eventObject to the handler:
Calls any handlers previously registered with the same event name.
Optional event object can be used as a argument, which gets passed
into a handlers as an argument.
eventbus.emit('eventName'[, eventObject]);
Unfortunately, there is no example and I don't know how passing the object works. I tried adding "something" (in this case the link that was clicked in the first component) to the call of the emit-function, but have no idea how/where I can read/use this nor if passing something as an eventObject works this way:
class navigation extends Component {
constructor(element) {
super(element);
//
// Define "Sub-components"
this.ref = {
navLinks: [],
};
}
mount() {
//
// Listen for clicks on nav.-links
for (let i = 0; i < this.ref.navLinks.length; i++) {
const link = this.ref.navLinks[i];
link.addEventListener("click", this.handleNavLinkClick.bind(this));
}
}
handleNavLinkClick(e) {
//
// Emit event
if (clickedLink.classList.contains("callHeader")) {
eventbus.emit("callingSubpageHeader", [e.target]);
}
}
}
It'd be great if someone could explain the concept and syntax of passing an eventObject in this scenario.
The event handler is passed the object from the event as a parameter, so your handler can grab that object as a variable from its function signature, like this:
handleEventBusCall_callHeader(target) {
console.log("The subpage-header was called.");
}
the variable target inside of your event handler is now equal to the object you passed with the event.
When you call the event, you don't need to put your argument in [], that will just put it into an array before passing it which will give you headaches later on. The brackets in the documentation just show that the second argument for eventbus.emit is optional.
I've created a Javascript object via prototyping. I'm trying to render a table dynamically. While the rendering part is simple and works fine, I also need to handle certain client side events for the dynamically rendered table. That, also is easy. Where I'm having issues is with the "this" reference inside of the function that handles the event. Instead of "this" references the object, it's referencing the element that raised the event.
See code. The problematic area is in ticketTable.prototype.handleCellClick = function():
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
cell1.addEventListener("click", this.handleCellClick, false);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
You can use bind which lets you specify the value that should be used as this for all calls to a given function.
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
Like always mdn is the best :). I just copy pasted the part than answer this question.
You need to "bind" handler to your instance.
var _this = this;
function onClickBound(e) {
_this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
cell1.attachEvent("onclick", onClickBound);
}
Note that event handler here normalizes event object (passed as a first argument) and invokes handleCellClick in a proper context (i.e. referring to an element that was attached event listener to).
Also note that context normalization here (i.e. setting proper this in event handler) creates a circular reference between function used as event handler (onClickBound) and an element object (cell1). In some versions of IE (6 and 7) this can, and probably will, result in a memory leak. This leak in essence is browser failing to release memory on page refresh due to circular reference existing between native and host object.
To circumvent it, you would need to either a) drop this normalization; b) employ alternative (and more complex) normalization strategy; c) "clean up" existing event listeners on page unload, i.e. by using removeEventListener, detachEvent and elements nulling (which unfortunately would render browsers' fast history navigation useless).
You could also find a JS library that takes care of this. Most of them (e.g.: jQuery, Prototype.js, YUI, etc.) usually handle cleanups as described in (c).
Also, one more way is to use the EventListener Interface (from DOM2 !! Wondering why no one mentioned it, considering it is the neatest way and meant for just such a situation.)
I.e, instead of a passing a callback function, You pass an object which implements EventListener Interface. Simply put, it just means you should have a property in the object called "handleEvent" , which points to the event handler function. The main difference here is, inside the function, this will refer to the object passed to the addEventListener. That is, this.theTicketTable will be the object instance in the belowCode. To understand what I mean, look at the modified code carefully:
ticketTable.prototype.render = function(element) {
...
var self = this;
/*
* Notice that Instead of a function, we pass an object.
* It has "handleEvent" property/key. You can add other
* objects inside the object. The whole object will become
* "this" when the function gets called.
*/
cell1.addEventListener('click', {
handleEvent:this.handleCellClick,
theTicketTable:this
}, false);
...
};
// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{
/*
* "this" does not always refer to the event target element.
* It is a bad practice to use 'this' to refer to event targets
* inside event handlers. Always use event.target or some property
* from 'event' object passed as parameter by the DOM engine.
*/
alert(event.target.innerHTML);
// "this" now points to the object we passed to addEventListener. So:
alert(this.theTicketTable.tickets.length);
}
This arrow syntax works for me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this will be the parent context and not the document context.
With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind or self = this:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}
[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881
According to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ,
my_element.addEventListener('click', (e) => {
console.log(this.className) // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this) // logs `false`
})
so if you use the arrow functions you can go safe beacause they do not have their own this context.
I know this is an older post, but you can also simply assign the context to a variable self, throw your function in an anonymous function that invokes your function with .call(self) and passes in the context.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.
What about
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while
bind(this) preserves the outerscope value of this inside the click event function.
If you want to get an exact target clicked, use e.target instead.
Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.
I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
// the callback array of methods to be run when
// event is triggered
this._callbacks = {handleCellClick:[this._handleCellClick]};
// assigned eventListenerInterface to one of this
// objects properties
this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
}
//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type)
{
this.parent = parent;
this.callback_type = callback_type;
}
//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
{
for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
//run the callback method here, with this.parent as
//this and evt as the first argument to the method
this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
}
}
ticketTable.prototype.render = function(element)
{
/* your code*/
{
/* your code*/
//the way the event is attached looks the same
cell1.addEventListener("click", this.handleCellClick, false);
/* your code*/
}
/* your code*/
}
//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
{
// this shouldn't work
alert(this.innerHTML);
// this however might work
alert(evt.target.innerHTML);
// this should work
alert(this.tickets.length);
}
The MDN explanation gives what to me is a neater solution further down.
In this example you store the result of the bind() call, which you can then use to unregister the handler later.
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as |this| is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as |this| is bound to newly created object
};
// bind causes a fixed `this` context to be assigned to onclick2
this.onclick2 = this.onclick2.bind(this);
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2, false); // Trick
}
const s = new Something(document.body);
In the posters example you would want to bind the handler function in the constructor:
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
cell1.addEventListener("click", this.handleCellClick, false);
// We could now unregister ourselves at some point in the future with:
cell1.removeEventListener("click", this.handleCellClick);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
While studying the source code for a signature pad widget, I find the following code snippet inside the constructor function (note in particular the comment in the following snippet):
var SignaturePad = function (canvas, options) {
...
// MY QUESTION IS ABOUT THE FOLLOWING CODE COMMENT!!!
// v v v
// we need add these inline so they are available to unbind while still having
// access to 'self' we could use _.bind but it's not worth adding a dependency
this._handleMouseDown = function (event) {
if (event.which === 1) {
self._mouseButtonDown = true;
self._strokeBegin(event);
}
};
// ... other event handlers here
...
}
... for completeness in providing context for the above code, later the event handlers are bound as event listeners:
SignaturePad.prototype._handleMouseEvents = function () {
...
this._canvas.addEventListener("mousedown", this._handleMouseDown);
...
};
From the above code snippet, you can see the comment:
we need add these inline so they are available to unbind while still having access to 'self'
we could use _.bind but it's not worth adding a dependency`
I am scratching my head about this. Why is access self required when unbinding (and I assume by 'unbinding' is meant detaching the event listener, but please correct me if I'm wrong)?
In other words, I'd like to understand the above code comment so that I can be certain I understand the JavaScript and/or event binding thoroughly in this code.
The .addEventListener calls in that code receive a function reference when binding the handler. In order to use .removeEventListener to unbind, you need to pass a reference to the same function handler.
Because the SignaturePad constructor creates a new, unique (though identical) function for each instance, and binds that function, they need to keep a reference to that function in order to unbind later on. Therefore they put it directly on the object for later use.
The reason they create these handlers inside the constructor function is that they want them to be able to reference the SignaturePad instance that was created. So they create a var self = this variable, and have the functions created in the constructor reference self. If the handlers were on the .prototype, there would be no way for that shared handler to reference the original object, given their approach.
Here's a truncated version of their code that shows how to use the EventListener interface:
var SignaturePad = function(canvas, options) {
this._handleMouseEvents();
};
// Implements the EventListener interface
SignaturePad.prototype.handleEvent = function(event) {
switch (event.type) {
case "mousedown":
this._handleMouseDown(event)
break
case "mousemove":
this._handleMouseMove(event)
break
case "mouseup":
this._handleMouseUp(event)
break
default:
console.log("Unbound event type:", event.type)
}
}
SignaturePad.prototype._handleMouseDown = function(event) {
if (event.which === 1) {
this._mouseButtonDown = true;
this._strokeBegin(event);
}
};
SignaturePad.prototype._handleMouseMove = function(event) {
if (this._mouseButtonDown) {
this._strokeUpdate(event);
}
};
SignaturePad.prototype._handleMouseUp = function(event) {
if (event.which === 1 && this._mouseButtonDown) {
this._mouseButtonDown = false;
this._strokeEnd(event);
}
};
SignaturePad.prototype._strokeUpdate = function(event) {
console.log("stroke update");
};
SignaturePad.prototype._strokeBegin = function(event) {
console.log("stroke begin");
};
SignaturePad.prototype._strokeEnd = function(event) {
console.log("stroke end");
};
SignaturePad.prototype._handleMouseEvents = function() {
this._mouseButtonDown = false;
this._canvas.addEventListener("mousedown", this);
this._canvas.addEventListener("mousemove", this);
document.addEventListener("mouseup", this);
};
So you can see that the handleEvent method was added, and we don't actually bind any functions using .addEventListener. Instead, we bind a reference to the SignaturePad object itself.
When an event occurs, the handleEvent method is invoked with the value of this pointing our SignaturePad object we bound. We still have access to the element as well via event.currentTarget.
So this lets us reuse functions on the .prototype and gives us all the object references we need. And of course unbinding is done the same way, except that we pass the object we bound to .removeEventListener.
I need to use jQuery events on non-DOM-related objects.
This works fine:
var o = {
}
$(o).on('bump', function () {
alert('ouch')
})
$(o).trigger('bump')
http://jsfiddle.net/d35bf35y/
But instead I need to attach an event on a property... the following code does not work.
var o = {
prop: 'test'
}
// Bind an event handler
$(o.prop).on('bump', function () {
alert('ouch')
})
// Trigger an event
$(o.prop).trigger('bump')
http://jsfiddle.net/d35bf35y/1/
In my real application that property will have an object.
I would like to know if is possible use jQuery in this way or an alternative solution.
"In my real application that property will have an object."
That part is rather important. This works:
var o = {
prop: {}
}
// Bind an event handler
$(o.prop).on('bump', function () {
alert('ouch')
})
// Trigger an event
$(o.prop).trigger('bump')
You can attach one on the Object and everytime it fires, you run a function that does something with your properties. (check if they're the same, or else)
var o = {
prop: 'test'
};
$(o).on('bump', function (e) {
var props = e.delegateTarget;
// do what you gotta do, like check if something has changed from before.
})
$(o).trigger('bump')
Or if your property is also an object, it should work.
This question already has answers here:
JavaScript: Class.method vs. Class.prototype.method
(5 answers)
Closed 9 years ago.
As i read through some examples of Angularjs' UI add-on, i've stumbled over some code that showed me that my knowdledge of Javascript is quite improvable:
The following is a class inside of an Angular provider:
function Dialog(opts) {
var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
this._open = false;
this.backdropEl = createElement(options.backdropClass);
if(options.backdropFade){
// ...
}
this.handleLocationChange = function() {
self.close();
};
// more functions
}
Pretty straightforward. But outside of that class, there are prototype functions, e.g the above invoked close()
Dialog.prototype.open = function(templateUrl, controller){
var self = this, options = this.options;
// .. some code
};
Now i do not understand why that function is declared as a prototype, but handleLocationChange inside the class itself.
How do i decide which method to choose?
The full gist can be found here
Consider these 2 cases:
Dialog.prototype.open = function...
Dialog.open = function....
First case - every object created by calling new Dialog() will have this open function
Second case has nothing to do with dialog objects, consider it as static function.
EDIT
found a great answer here : javascript-class-method-vs-class-prototype-method
function open will be shared by all objects create using new Dialog().. and handleLocationChange will be different for different objects.
I think handleLocationChange is called from event triggering object that registers listeners but doesn't register the this context so when it's triggered you can't use this as it refers to handleLocationChange. To overcome this they have chosen to set a closure reference to this (=the self variable) and call other instance functions using self. Basically it's storing a value known at creation but not known when handleLocationChange is executing.
Here is some code showing the problem:
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this.toString());
// logs this is now: function (){ console.log("this is now:...
// so this is now the sayName function not the person instance
console.log(this.name);//undefined: sayName doesn't have a name property
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is how it's solved setting a closure reference
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
var self=this;// set closure ref to this
this.name=name;
this.sayName=function(){
console.log(self.name);//use closure ref to get this
// logs jon
}
};
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is a fix to the event system to take care of the this context:
var eventSystem={
events:{},
add:function(eventname,fnCallback,thisRef){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push({
"callback":fnCallback,//store the event handler
"thisRef":thisRef//store the this context
});
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i].callback.call(
this.events[eventname][i].thisRef);
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this);//referring to person instance
// with the name jon
console.log(this.name);//logs jon
console.log(this instanceof person);//true
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName,jon);//add extra parameter for this ref
eventSystem.trigger("sayname");//trigger the event
The pattern used above is not an event system (think it's pulisher subscriber) as an event usually get triggered on or invoked from an object (button, input, dialog) but in case of a more event system like implementation it would be easy to get the correct this context since you trigger the event on or from an instance (like myButton or myDialog).
See following code for event system like implementation:
var eventSystem={
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
//change in trigger as it's passing the event object now
trigger:function(event){
if(!this.events[event.type]){
return;
}
var i=0;
for(i=0;i<this.events[event.type].length;i++){
this.events[event.type][i](event);
}
},
initES:function(){//set the instance variables needed
this.events=this.events||{};
}
};
function addProtos(o,protos){
for(item in protos){
o.prototype[item]=protos[item];
}
}
var person=function(name){
this.name=name;
this.initES();//needed to initialeze eventsystem
};
// make person capable of storing event handlers
// and triggering them
addProtos(person,eventSystem);
person.prototype.askQuestion=function(){
//asking a question will trigger an "answer" event
this.trigger({type:"answer",target:this});
}
// handler for when jon will fire an answer event
function answerHandler(event){
console.log("answer from:",event.target);
console.log("name of the person:",event.target.name);
}
var jon=new person("jon");
jon.add("answer",answerHandler);//add event listener
jon.askQuestion();//triggers the answer event from within jon
jon.trigger({type:"answer",target:jon});//trigger the event externally
Not sure why Angular choose to "break" prototype by using closures as the examples show there are other alternatives. Maybe someone can explain that who is more familiar with Angular.