Binding events to functions within objects - javascript

I'm running into a peculiar issue with jQuery/JS in general.
I'm working on a JS file that let me create portlets for a client site. I'm using SignalR to communicate changes to the users.
The following piece of code gives me a bit of a headache as to why it won't work.
Eenheden: function (poPortlet) {
this.moPortlet = poPortlet;
this.Init = function () {
$(this.moPortlet).find('.portlet-title').text($(this.moPortlet).attr('data-cpv-type') + ' Eenheden')
};
this.BindHub = function () {
CPV.moHub.on('onEenheidUpdate', this.Events.Update);
};
this.Events = {
Update: function (pnId, psStatus) {
CPV.Log('Error', 'Update: ' + pnId + ' ' + psStatus);
}
};
}
I am trying to bind the function this.Events.Update on the SignalR event onEenheidUpdate. Instances of these Eenheiden objects are not unique on the pages. The idea is that they contain the same data, but can get filtered, creating a different portlet depending on some configs.
My problem is that the onEenheidUpdate function doesn't trigger the proper event. I want to do it like this so I can use references I set for the unique object, such as the jQuery object I set on initialization.

Problem
Your problem is that when jQuery or javascript in general triggers an event callback, the "context" (value of this) is changed. For example
var MyModule = {
init: function () {
$('.amazing-element').on('click', function () {
// This breaks because `
// `this` is not MyModule
// When the callback is triggered
// `this` is `<p class="amazing-element"></p>`
this.onClick()
})
},
onClick: function () {
console.log('wee, I was clicked')
}
}
Function.prototype.bind to the rescue!
var MyModule = {
init: function () {
$('.amazing-element').on('click', function () {
this.onClick()
}.bind(this))
// ^ here we bind the context of the callback to the
// correct version of `this`.
},
onClick: function () {
console.log('wee, I was clicked')
}
}
So your exact example would look like:
Eenheden: function (poPortlet) {
this.moPortlet = poPortlet;
this.Init = function () {
$(this.moPortlet).find('.portlet-title').text($(this.moPortlet).attr('data-cpv-type') + ' Eenheden')
};
this.BindHub = function () {
CPV.moHub.on('onEenheidUpdate', this.Events.Update.bind(this));
// ^ change here
};
this.Events = {
Update: function (pnId, psStatus) {
CPV.Log('Error', 'Update: ' + pnId + ' ' + psStatus);
}
};
}

Related

Can't remove event listener for windows object

I am having a lot of trouble trying to remove an event listener.
I have created a website that relies on JavaScript quite heavily. When you navigate on the website it is basically loading in elements dynamically without a page refresh with template literals.
I have to sometimes load in content and add infinite scroll but also be able to remove that event again.
This is the code I use to handle scroll events:
var start = 30;
var active = true;
function yHandler(elem)
{
var oHeight = selectElems('content_main', 'i').offsetHeight;
var yOffset = window.pageYOffset;
var hLimit = yOffset + window.innerHeight;
if (hLimit >= oHeight - 500 && active === true)
{
active = false;
new requestContent({
page: GET.page,
type: returnContentType(GET.page),
scroll: true,
start: start
}, (results) => {
if(results){
setTimeout(()=>{
active = true;
start = start + 30;;
}, 400);
new ContentActive();
}
});
}
}
var scrollRoute =
{
contentScroll: () =>{
yHandler();
}
};
var scrollHandler = function(options)
{
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
this.events = () => {
addEvent(elem, 'scroll', ()=>{
scrollRoute[func](elem, funcOptions);
}, flag);
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
}
}
I am using this function to set events
function addEvent(obj, type, fn, flag = false) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, flag);
} else if (obj.attachEvent) {
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event);
};
obj.attachEvent("on" + type, obj[type + fn]);
} else {
obj["on" + type] = obj["e" + type + fn];
}
}
I am calling this code from whatever code when I need to set the infinite scroll event
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).events();
I am calling this code from whatever code when I need to remove the infinite scroll event but without any luck
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).clear();
How do I successfully remove the event listener? I can't just name the instances, that will be so messy in the long run when setting and removing the scroll events from various different places.
Two problems:
You have to pass the same function to removeEventListener as you passed to addEventListener. (Similarly, you have to pass the same function to detachEvent as you passed to attachEvent using Microsoft's proprietary stuff — but unless you really have to support IE8 and earlier, you can ditch all that.) Your code isn't doing that.
When trying to remove the handler, you're calling scrollRoute[func]() and passing its return value into removeEventListener. As far as I can tell, that's passing undefined into removeEventListener, which won't do anything useful.
Here's the code I'm referring to above:
this.events = () => {
addEvent(elem, 'scroll', ()=>{ // *** Arrow function you don't
scrollRoute[func](elem, funcOptions); // *** save anywhere
}, flag); // ***
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
// Calling rather than passing func −−−^^^^^^^^^^^^^^^^^^^
}
Notice that the function you're passing addEvent (which will pass it to addEventListener) is an anonymous arrow function you don't save anywhere, but the function you're passing removeEventListener is the result of calling scrollRoute[func]().
You'll need to keep a reference to the function you pass addEvent and then pass that same function to a function that will undo what addEvent did (removeEvent, perhaps?). Or, again, ditch all that, don't support IE8, and use addEventListener directly.
So for instance:
var scrollHandler = function(options) {
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
var handler = () => {
scrollRoute[func](elem, funcOptions);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};
(Notice I added a couple of missing semicolons, since you seem to be using them elsewhere, and consistent curly brace positioning.)
Or using more features of ES2015 (since you're using arrow functions already):
var scrollHandler = function(options) {
const {elem, flag, func: {name, options}} = options;
const handler = () => {
scrollRoute[name](elem, options);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};

Using Bacon.js with FlightJS

Is Bacon compatible with Twitter's Flight? I saw this talk where they are apparently being used together (https://www.youtube.com/watch?v=D0N1NdE-9u0) but couldn't get a minimal example to work.
This is my flight component with traditional event handling alongside Bacon's event stream.
The latter just logs undefined for the data.a and it turns out what's being passed to the function registered with onValue is actually the event object (named e in the traditional handler function) with no access to the data object.
define(function (require) {
'use strict';
var c = require('flight/lib/component'),
$ = require('jquery'),
B = require('bacon');
$.fn.asEventStream = B.$.asEventStream;
return c(f);
function f() {
this.after('initialize', function () {
// traditional handler
this.on('dummyData', function (e, data) {
console.log('jquery: ' + data.a);
});
// Bacon handler
this.$node.asEventStream('dummyData').onValue(function (data) {
console.log('bacon: ' + data.a);
});
// emit data object
this.trigger('dummyData', { a: 'b' });
});
}
});
You can pass an optional argument to asEventStream function that you can use to map the event payload:
this.$node.asEventStream('dummyData', function(e, data) {
return data;
}).onValue(function (data) {
console.log('bacon: ' + data.a);
});
Take a look at the examples here: https://github.com/baconjs/bacon.js#%24-aseventstream

combine multiple functions with same code in jquery

Yes, I have thoroughly searched google and did not find anything that suits my requirement.
The code i have so far is at the link below:
http://jsfiddle.net/ZKwTY/4/
There are multiple onchange events which call almost the same code, i would like to combine them maybe in a comma separated fashion to call it only once.
something like this
(on1Change, on2Change, on3Change): function () {
this.loadData();
}
is this possible??
Note: these functions are bound to the controls via a framework over which i do not have control, i need to create these functions and the framework would bind these to the respective controls
or you can create your object like this
var ol = {
on1Change: this.loadData,
on2Change: this.loadData,
on3Change: this.loadData,
on4Change: this.loadData,
loadData: function () {
this.loadData1();
this.loadData2();
},
loadData1: function () {
alert('hi from loadData1');
},
loadData2: function () {
alert('hi from loadData2');
}
};
Then if you want to do it once, then declare a object
var ol = {
loadData: function () {
this.loadData1();
this.loadData2();
},
loadData1: function () {
alert('hi from loadData1');
},
loadData2: function () {
alert('hi from loadData2');
}
};// end of object
ol.on1Change = ol.on2Change = ol.on3Change = ol.on4Change = ol.loadData;
add all propteries dynamically after object declaration
use bind()
$("selector").bind(on1Change, on2Change, on3Change): function () {
this.loadData();
}.....
you can try somethig like this http://jsfiddle.net/s4VVY/
i.e. add methods after object create
[1,2,3,4,5].forEach(function(it){ol["on"+it+"Change"] = function(){this.loadData()}})
UPDATE
may be this help
var ol = (function(){
var o = {
loadData: function () {
this.loadData1();
this.loadData2();
},
loadData1: function () {
alert('hi from loadData1');
},
loadData2: function () {
alert('hi from loadData2');
}
}
o.on1Change=o.on2Change=o.on3Change=o.on4Change=function(){ this.loadData();};
return o;
})()
also you can make function bindFunc
function bindFunc(){
var obj = arguments[0],
handler = arguments[1],
properties = Array.prototype.slice.call(arguments,2);
for(var i in properties){
obj[properties[i]] = handler;
}
}
and call as
bindFunc(o,function(){this.loadData();},"on1Change","on2Change","on3Change","on4Change")

Passing instance classes in WinJS

I'm trying to pass a class instance about so that I can persist its member variables. I have the following code
var mainNamespace = WinJS.Namespace.define("MainNamespace", {
setupClass: WinJS.Class.define(
function () { },
{
createSetup: function CreateSetup() {
var interactionInst = new mainNamespace.interaction();
drawScreen.DrawScreen(interactionInst);
var backgroundProc = new
mainNamespace.createProc(interactionInst);
}
),
interaction: WinJS.Class.define(
function() {},
{
clickedPos: 0,
handleTouch: function handleTouch(event) {
this.clickedPos = event.x;
console.log("New pos: " + this.clickedPos);
}
),
createProc: WinJS.Class.define(
function (interaction) {
setInterval(this.runProc, 1000, interaction);
},
{
runProc: function runNewProc(interaction) {
console.log(interaction.clickedPos);
}
...
The drawScreen namepsace is as follows:
WinJS.Namespace.define("drawScreen", {
DrawScreen: WinJS.Class.define(
function DrawScreen(interaction) {
/// Do some screen set-up here
canvas.addEventListener("MSPointerUp", interaction.handleTouch, false);
}
)
});
The problem I have is that the interaction.clickedPos never changes. Why does it not change, and am I going about this the right way for a javascript app?
EDIT:
I've now worked out WHY this is happening, but not how to fix it. When interaction.handleTouch fires, this refers to the canvas object, and NOT the interaction object - so I have no access to it's members.
The problem is on the following line:
canvas.addEventListener("MSPointerUp", interaction.handleTouch, false);
here you are passing a reference to the function handleTouch() without any connection to the current instance (interaction). You can change the context of the event handler by using the .bind() method:
canvas.addEventListener("MSPointerUp", interaction.handleTouch.bind(interaction), false);

Invoke public function from internal/private function?

Just wondering if I'm missing something or not but I attempted to do the following:
(function() {
var thing = function() {
var doIt = function() {
console.log("just do it");
this.updateValue(5);
};
return {
updateValue: function(val) {
console.log('updating value: ' + val);
},
go: function() {
doIt();
}
}
};
var t = thing();
t.go();
}())
This results in "just do it" showing up in the console followed by an error b/c it says "updateValue" is not a function.
I was wondering, can an internal/private function (e.g. "doIt") invoke a public function (e.g. "updateValue")? Perhaps this is just bad design and you should never really want to do this and I've actually refactored my code to avoid/not do this but I was curious if it was possible.
Thanks in advance.
Either use call/apply to explicitly specify the context for this (like #SLaks and #Alnitak) mentioned or else define the function at the beginning and then add it as a property to the returned object:
var thing = function() {
var updateValue = function () { /* */ },
doIt = function() {
console.log("just do it");
updateValue(5);
};
return {
updateValue: updateValue, // minor duplication here
go: function() {
doIt();
}
};
};
If the minor duplication annoys you, you can also do this:
var thing = function() {
var exposed = {
updateValue: function(val) {
console.log('updating value: ' + val);
},
go: function() {
doIt();
}
}, doIt = function() {
console.log("just do it");
exposed.updateValue(5);
};
return exposed;
};
Writing doIt(), calls the function in the global context, so this is the window object.
You need to write doIt.call(this) to pass your this as the context for doIt.
Per #SLaks answer, this is incorrect when invoked by doIt().
Instead, try:
doIt.call(this);

Categories