Knockout event binding unexpected behaviour - javascript

I have an event binding for the scroll event on a DIV. For debouncing the handler I introduced a function on my model which creates the debounced handler, and I'm binding this factory function in my view.
I would expect that my factory creates the debounced function and knockout will bind that to the event. Instead, it seems like knockout recreates and calls my debounced function at every event trigger, so debouncing doesn't work at all.
My view
<div data-bind="event.scroll: getScrollHandler()"></div>
My model
var viewModel = {
getScrollHandler: function(data, evt) {
return debounceFunction(function(data, evt) {
// dp the actual handling...
});
}
};
I would expect that the getScrollHandler method would execute only once at binding initialization time, and it would bind the returned function.
Instead, it seems like knockout is wrapping it all to a new function so it runs on every scroll event.
How exactly does it work in Knockout?
UPDATE
As I'm using TypeScript and this handler is a member method of a class, I'm limited to this kind of function member assignment, I cannot directly assign the debounced function as a member (or actually I could, but in some uglier way only).

Assuming you have an implementation similar to this one, the idea is it creates a new function which you then use in place of your original function. Try changing your code to this:
getScrollHandler: debounceFunction(function(data, event) {
...
})
This will create the function once and re-use it every time the scroll is activated.

Related

React JS: calling event handlers

I have started working in React recently. I noticed one behaviour like when I am trying to call my event handle with the same component like this
onClick={someEventHandler} it is triggering but when I try to do the same same like this onClick={()=> someEventHandler} it doesn't work.
I noticed that when I need to pass any arguments and calling the function like onClick={()=>someEventHandler(id)} it is working fine.
can some one explain me the logic/theory behind this?
The onClick event handler needs a function to be passed to it. Whenn the event is triggered, it calls the handler function passed to it.
In the first case it works because you pass someEventHandler to onClick which is invoked when event is fired. An invocation to the function is like someEventHandler()
Now in the second case,
onClick={()=>someEventHandler}
the function passed to onClick is ()=>someEventHandler which can be elaborated further as ()=> { return someEventHandler; }
Now if you notice above you are returning a function from the onClick event handler. The returned function is now never invoked and hence you see the above behavior
It works in the last case like onClick={()=>someEventHandler(id)}, because when the event handler is invoked, it invoked someEventHandler with id too.
However you do not need to pass id to invoke it, you can simply use it like onClick={()=>someEventHandler()} and it work, provided you don't need id as a parameter in someEventHandler.
Another thing to note when you are using function like onClick={()=>someEventHandler()} instead of onClick={someEventHandler} is that your someEventHandler will not be invoked with any arguments. If you want the event to be passed as argument to someEventHandler, you need tto explicitly pass it like
onClick={(e)=>someEventHandler(e)}
onClick={someEventHandler} will trigger the handler as soon as your component gets loaded/rendered on the screen but by adding an arrow function before the handler like this - onClick={()=> someEventHandler} will make sure not to trigger the component before you click on it

How to pass extra parameter to event handling callback?

I have a button on which I want to attach an event listener. I also need to pass a extra parameter url to this function. I read about apply and I'm doing the following:
$('#list-button').on('click',postListing.apply([url]));
My problem is that as soon as this script is loaded postListing is called. I am not calling the function anywhere else. I need it to be called only on click.
The difference between bind and call/apply is that bind doesn't call the function immediately much like it loads the data with the variable when needed
You can reformat your code so it looks like this
$('#list-button').on('click', postListing.bind(this, url));
Found a way. It can be done using a closure:
var postListing = function(event, url){
return function(){
//Main functionality wrapped here
};
};
And the event listener setting remains the same:
$('#list-button').on('click',postListing.apply([url]));

How to unbind $on in Angular?

I have a code that use $scope.$on one time on init and then in a function, so the code is executed multiple times. How can I unbind if first before I bind it again. I've try $scope.$off but there's not such function, https://docs.angularjs.org/api say nothing about $on. I'm using angular 1.0.6.
If you don't un-register the event, you will get a memory leak, as the function you pass to $on will not get cleaned up (as a reference to it still exists). More importantly, any variables that function references in its scope will also be leaked. This will cause your function to get called multiple times if your controller gets created/destroyed multiple times in an application.
Fortunately, AngularJS provides a couple of useful methods to avoid memory leaks and unwanted behavior:
The $on method returns a function which can be called to un-register the event listener.
Whenever a scope gets cleaned up in Angular (i.e. a controller gets destroyed) a $destroy event is fired on that scope. You can register to $scope's $destroy event and call your cleanUpFunc from that.
See the documentation
Sample Code:
angular.module("TestApp")
.controller("TestCtrl",function($scope,$rootScope){
var cleanUpFunc = $scope.$on('testListener', function() {
//write your listener here
});
//code for cleanup
$scope.$on('$destroy', function() {
cleanUpFunc();
};
})
$scope.$on returns a function which you can call to unregister.

How to pass the event argument of jQuery .click() to a non-anonymous function

What is the proper way to accomplish the following:
$("#btn").click(function1);
Calling the function:
function function1 (event) {
event.preventDefault();
}
This seems to work, however I don't understand how function1 understands what the event argument is referring to without it being passed in. Wouldn't a listener set up like this make more sense:
$("#btn").click(function1(event));
Here is a fiddle.
The .click() function in jQuery except as first parameter a function. In Javascript function are value, as well as a primitive value or an object. Functions are first-class citizens.
If you use function1(event) as a parameter, the function will be executed, because this is the semantic of the brachet after the function name. So the .click() jQuery function will receive the output of the function, which is not the expected type.
Passing the function name as a parameter means that you are passing the function (actually, a reference to the function), not the result of the function invocation. And the function will be called when the click event will be triggered. The function in this case is called "callback".
Callbacks are very importants in Javascript, since the event-driven behaviour is the main reason for using a client-side scripting.
The concept behind the callback system is
//the click function
function doSomething(callback){
//in your case the event is the argument that jQuery will prepare for you
var argument = produceTheArgument();
//doSomething is in charge to invoke the function, passing the argument
callback(argument);
}
//your function
function myCallback(argument){
//your function will consume the argument
}
//my callback is passed as a reference, not invoked
doSomething(myCallback);
you are subscribing to event and passing a reference to the function inside click listener - the jQuery event processor will just call your function in jQuery's context and will pass all parameters to it.
In your first example function1 knows that the event variable is, because JavaScript (and subsequently jQuery) passes the event information as a parameter.
This is the nature of JavaScript, not just jQuery. Consider the following:
document.getElementById('btn').addEventListener('click', function1, false);
function function1(e)
{
console.log(e);
}
JavaScript automatically calls function1 when #btn is clicked, and it automatically adds the event information as the first parameter. jQuery simply passes this information into its own methods as well, so that you have access to it.
According to jQuery's documentation:
The click event is sent to an element when the mouse pointer is over the element, and the mouse button is pressed and released. Any HTML element can receive this event.
Reference: http://api.jquery.com/click/

Cannot unbind jQuery custom event handler

I have a chunk of markup in my page that represents a view, and a JS controller function which is associated with that view. (These are Angular, but I don't believe that matters.) The controller code listens for a custom event fired from elsewhere in the app, and handles that event with some controller-specific logic.
My problem is that the controller's event handler is getting attached too many times: it gets attached every time the view is re-activated, resulting in the handler being run multiple times every time the custom event is fired. I only want the handler to run once per event.
I've tried using .off() to unbind the handler before binding it; I've tried .one() to ensure that the handler is only run once; and I've tried $.proxy() after reading about its interaction with .off() here.
Here's a sketch of my code:
// the code inside this controller is re-run every time its associated view is activated
function MyViewController() {
/* SNIP (lots of other controller code) */
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
// Three variants of the same misbehaving event attachment logic follow:
// first attempt
$('body').off('myCustomEvent', myCustomEventHandler);
$('body').on('myCustomEvent', myCustomEventHandler);
// second attempt
$('body').one('myCustomEvent', myCustomEventHandler);
// third attempt
$('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
$('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));
// all of these result in too many event attachments
};
// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
$('body').trigger('myCustomEvent');
console.log('myCustomEvent has been triggered');
};
After clicking around in my app and switching to the troublesome view five times, then doing the action which runs MyEventSender, my console will look like this:
myCustomEvent has been triggered
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
How can I get it to look like this:
myCustomEvent has been triggered
myCustomEventHandler has run
???
Give your events a namespace, then simply remove all events with said namespace when you re-run the controller.
jsbin
$('body').off('.controller');
$('body').on('myCustomEvent.controller', myCustomEventHandler);
You could listen in on the scope destroy event in your Main controller
function MyViewController($scope) {
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
$('body').on('myCustomEvent', myCustomEventHandler);
$scope.$on("$destroy", function(){
$('body').off('myCustomEvent', myCustomEventHandler);
//scope destroyed, no longer in ng view
});
}
edit This is an angularJS solution. The ngview is constantly being loaded as you move from page to page. It will attach the event over and over again as the function is repeatedly called. What you want to do is unbind/remove the event when someone leaves the view. You can do this by hooking into a scopes $destroy (with the dollar sign) event. You can read up more on that here: $destroy docs
The problem is that when function MyViewController(){} is called multiple times, you get a separate instance of myCustomEventHandler (attached to the current closure), so passing that to $.off doesn't unregister the previous handler.
KevinB's answer, event namespaces, is what I suggest for removing specific handlers without requiring knowledge of which handler was installed. It'd be nicer if you could unregister the events when the element is removed/hidden, then you would have the reference to the function you want to unregister, without risking removing handlers that other code may have added to the same event namespace. After all, event namespace is just a global pool of string and is susceptible to name collision.
If you make your function global, it will also work (except that it looks like you need the closure), but I'm just showing it to explain the problem, use namespaces
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
function MyViewController() {
// first attempt
$('body').off('myCustomEvent', myCustomEventHandler);
$('body').on('myCustomEvent', myCustomEventHandler);
// second attempt
$('body').one('myCustomEvent', myCustomEventHandler);
// third attempt
$('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
$('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));
}
// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
$('body').trigger('myCustomEvent');
console.log('myCustomEvent has been triggered');
}
MyViewController();
MyViewController();
MyEventSender();
Previous Idea
One of the problems is that you're not passing the same function to $.on and $.off, so off is not unregistering anything in this case
Not the problem, leaving the answer up for reference since it's not exactly intuitive. $.proxy seems to return a reference to the same bound function if passed the same function and context. http://jsbin.com/adecul/9/edit

Categories