Clean way to pass parameters between JavaScript events? - javascript

I have values I would like to keep in memory when certain events fire, like when a user is dragging and I want to save the element index. The value is needed when another event might fire. I don't know if it will ever fire.
I am using global variables to keep these values around. It seems there are better ways to do this like putting these values in an object with a namespace. Benefits? Any other suggestions?

My alltime favorite is currying variables into event handlers. Curry is a function prototype and when called on a function it will return a version of the function with preset arguments:
Function.prototype.curry = function curry() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function curryed() {
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};
};
node.addEventListener('mousedown', handler.curry(var1,var2,etc));

You could bind the data to a parent element that is a relevant container for each value. So for example, say you have a #dragAndDropContainer, in which there are many drag and droppable items, then as soon as a drag event fires (i.e. a drag begins), you could (in jQuery), execute:
$('#dragAndDropContainer').data('lastDragged', $(this).attr('id'));
And then just query $('#dragAndDropContainer').data('lastDragged') every time you need to.

I like davin's solution, but here is an alternative if that doesn't suit you. You can create a closure around the event handler like so:
var data = { x: 1, y: 2 };
var handler = function() {
console.log(data);
}
if (node.addEventListener) {
node.addEventListener('mousedown', handler, false);
} else {
node.attachEvent('onmousedown', handler);
}

An alternative to using Function.bind (sometimes known as curry): You can control the scope of your shared variable. Here's some jQuery pseudo-code demonstrating it. Shared is accessible to those two handlers and nobody else.
$(function(){
var shared = {a:1, b:2};
$('#id-1').click(function() {
alert(shared.a);
});
$('#id-2').click(function() {
alert(shared.b);
});
});
If you're writing jQuery procedural code, the closure approach is much simpler. Since most everything I write is an object, I'd rather not get into too many levels of inner closures, so I prefer to setup a handler with binding/currying (like Martin's example) if a handler needs access to shared variables.

Related

How do you remove an event listener that uses an anonymous function for passing parameters?

Apologies in advance as I have done some searching and this appears to be a fairly common question, but none of the answers I have found quite meet my needs. The closest I was able to find was How do I add and remove an event listener using a function with parameters?, but the answer there involves JQuery and I am trying to find a way to do this in just JS.
I am using anonymous functions to pass parameters through an event trigger which I believe is the correct way to do so. If temp is defined by some calculations based on the state at the time the event is added, I want to add the listener as follows:
item.addEventListener("click", function(){myOnClickFunction(temp)});
However, I want to be able to remove the event dynamically if certain conditions are met.
item.removeEventListener("click", function(){myOnClickFunction(temp)});
This does not work as the inline function is anonymous and cannot be referenced for matching up the event listener (plus temp is likely different anyway). Since temp is calculated at the time of the trigger, I cannot store a reference outside of the anonymous function with something like this:
var listener = function() {
var temp = calculate(arg1, arg2, event);
myFunction(temp);
};
window.addEventListener('click', listener, false);
so that I can later call:
window.removeEventListener('click', listener, false);
myOnClickEvent(temp){
//My code and calculations here
document.removeEventListener("keypress", arguments.callee);
}
is also not working, although I'm less confident as to how that method is supposed to work.
How can I remove this function dynamically? In some cases, I might be able to refactor my code so that all variables that need to be passed are stored globally and rewritten constantly, but that sounds like messy code. Plus, in some cases the trigger event is one of the arguments that needs to be passed so I don't think I could make that happen. Any ideas?
You can create an object, whose properties are the temp values, and whose values are myOnClickFunction bound to that temp. For example:
const boundFns = {};
// acquire temp somehow
const boundFn = () => myOnClickFunction(temp);
boundFns[temp] = boundFn;
item.addEventListener("click", boundFn);
Then, when you need to remove the listener, retrieve the appropriate bound function:
item.removeEventListener("click", boundFns[temp]);
If a temp may be used more than once, check if it exists in boundFns first:
const boundFns = {};
// acquire temp somehow
if (!boundFns[temp]) {
boundFns[temp] = () => myOnClickFunction(temp);
}
const boundFn = boundFns[temp];
boundFns[temp] = boundFn;
item.addEventListener("click", boundFn);
If temp cannot be be used reliably as a unique object key (for example, if it's an HTMLElement), you can use a Map instead, which is like an object, but whose keys can be anything, not just strings:
const boundFns = new Map();
boundFns.set(temp, boundFn);
// ...
item.removeEventListener("click", boundFns.get(temp));

How to pass event and other arguments to click handler

Hey I have a build a canvas using easelJS.
In my canvas, I have points which a click handler is define for them using the following syntax:
p.on("click", handleMouseClickEvent);
Now I want to pass arguments to the handler handleMouseClickEvent , I know that I get the event object for free without passing it, but when I try to pass one argument, lets say I write:
p.on("click", handleMouseClickEvent(arg1));
Then the event object is undefined and not accessible at all.
How can I pass the event object and many more arguments using the above syntax.
p.on("click", handleMouseClickEvent(arg1,arg2,...,argN));
When using jQuery, Ravi's answer is perhaps the best way.
I'll try to provide another perspective to solve your question.
By using
p.on("click", handleMouseClickEvent(arg1));
you're not passing the function as event handler, you're executing it and passing its return value as event handler.
That perhaps already pointed you to the answer, right?
Try to define your event handler like this:
function handleMouseClickEvent(arg1)) {
return function reallyHandleMouseClickEvent(event) {
// all variables available here: arg1, event
}
}
Of course, you can add as many argN parameters as you want.
Since you're not calling the handler function yourself (the browser is doing it for you), you don't get to specify further arguments to the handler. All you'll get is the event object.
However, because of how JavaScript's variable scoping works, you don't have to. Functions have access to variables outside themselves, so you can simply access variables without passing them in explicitly:
var foo = "bar";
var handleMouseClickEvent = function(e) {
console.log(e.type); // 'click'
console.log(foo); // 'bar'
};
p.on("click", handleMouseClickEvent);
You need to define your event handler, try this:
function handleMouseClickEvent(arg1)) {
return function doSomething(event) {
//logic here
}
}
you can try this:
p.on("click", null, {arg1: "abc", arg2: "xyz"},handleMouseClickEvent);
//And in your function, you can get the event data like this
function handleMouseClickEvent()
{
alert(event.data.arg1);
alert(event.data.arg2);
}
According to official documenatation, on takes arguments like this:
.on( events [, selector ] [, data ], handler )
events Type: String
One or more space-separated event types and optional namespaces, such as
"click" or "keydown.myPlugin".
selector Type: String
A selector string to filter the descendants of the selected elements that > > > trigger the event. If the selector is null or omitted, the event is always
triggered when it reaches the selected element.
data Type: Anything
Data to be passed to the handler in event.data when an event is triggered.
handler Type: Function( Event eventObject [, Anything
extraParameter ] [, ... ] )
A function to execute when the event is
triggered. The value false is also allowed as a shorthand for a
function that simply does return false.
For more information, see JQuery Documentation of on() event Handler
Just to throw a more context-specific answer into the mix. I believe this question was asked in reference to this codepen: http://codepen.io/Barak/pen/AXZxKN
From an architecture standpoint, injecting parameters into handlers is a workaround you don't need. Instead, use the event target to access what was clicked/interacted with, and determine the values you need.
For your example, you are looking for the original data properties used to plot the points in your graph. You can either inject those properties onto the display object, inject a reference to the original data object, or create a look-up table to associate the display object with its related data point.
for (...) {
var point = data[i];
var dot = new createjs.Shape();
dot.x = point.x * GRAPH_WIDTH;
// Inject property or reference
dot.point = point;
// Create lookup
lookupTable[i] = dot;
}
Then when you click the object, look up the data.
dot.on("click", function(event) {
var dot = event.target;
// Use reference
var point = dot.point;
// Or use lookup
var index = lookup.indexOf(dot);
//...do stuff with it
}
There are lots of other ways to create this relationship, these are just some suggestions. Creating wrapper functions will work, but IMHO it is not a great approach for the long term or for a larger application. You can totally continue to use your approach, as it appears to be working for you -- but I wanted to offer some food for thought.
Cheers.
This should handle it. You can add more arguments.
(function(arg1, arg2) {
p.on("click", function(event) { handleMouseClickEvent(event, arg1, arg2) })
})(arg1, arg2);
The arguments can be bound to a handler function with Function.prototype.bind
p.on("click", handleEventWithArg.bind(this,arg1));
function handleEventWithArg(arg1, event) {
console.log(arg1);
console.log(event);
});
The .bind method returns a new functon that the browser can invoke with the event object.

jQuery Plugins: Unexpected behavior - context of "this" is probably lost

I've written a really basic jQuery plugin that should log the value of a button clicked.
While it basically works, it unfortunately logs the same value for each button on the page. So if there are three buttons on the page, the value of the button clicked is logged three times.
The plugin code looks like this:
(function($) {
$.fn.content = function() {
return this.each(function() {
$('button').click(function() {
console.log($(this).html());
});
});
};
}(jQuery));
$('button').content();
Fiddle
Although I am quite puzzled right now, I am pretty sure that it has something to do with the context of this inside the each loop, but I can't really figure it out.
There are several issues going on here. The this inside of the fn.content function is actually the composed jQuery object. That object was $('button') so it will contain how ever many buttons there are in the DOM at that moment.
Then you iterate that set, and for each item in that set, you attach a click handler again to the $('button') group explicitly. Unfortunately this means that you have assigned a click event handler to that group n times where n is how many buttons there were. 3 buttons, 3 events; 6 buttons, 6 events.
In order to not do that, simply assign the click handler to the original jQuery object.
(function($) {
$.fn.content = function() {
return this.click(function() {
console.log($(this).html());
});
});
}(jQuery));
Keep in mind that click will internally call .each, and that this in our example is $('button') already.
For more information on creating jQuery plugins, please see my post on the utility of jQuery.fn
I'm not sure which scope you actually need access to, but scope of this is different for each function, including anonymous functions that you created. To access this of an outer scope, try something like the following:
function foo() {
this.someVal = 1;
var that = this;
function bar() {
console.log(that.someVal); // you have access to foo's this
}
}

How to assign event callbacks iterating an array in javascript (jQuery)

I'm generating an unordered list through javascript (using jQuery). Each listitem must receive its own event listener for the 'click'-event. However, I'm having trouble getting the right callback attached to the right item. A (stripped) code sample might clear things up a bit:
for(class_id in classes) {
callback = function() { this.selectClass(class_id) };
li_item = jQuery('<li></li>')
.click(callback);
}
Actually, more is going on in this iteration, but I didn't think it was very relevant to the question. In any case, what's happening is that the callback function seems to be referenced rather than stored (& copied). End result? When a user clicks any of the list items, it will always execute the action for the last class_id in the classes array, as it uses the function stored in callback at that specific point.
I found dirty workarounds (such as parsing the href attribute in an enclosed a element), but I was wondering whether there is a way to achieve my goals in a 'clean' way. If my approach is horrifying, please say so, as long as you tell me why :-) Thanks!
This is a classic "you need a closure" problem. Here's how it usually plays out.
Iterate over some values
Define/assign a function in that iteration that uses iterated variables
You learn that every function uses only values from the last iteration.
WTF?
Again, when you see this pattern, it should immediately make you think "closure"
Extending your example, here's how you'd put in a closure
for ( class_id in classes )
{
callback = function( cid )
{
return function()
{
$(this).selectClass( cid );
}
}( class_id );
li_item = jQuery('<li></li>').click(callback);
}
However, in this specific instance of jQuery, you shouldn't need a closure - but I have to ask about the nature of your variable classes - is that an object? Because you iterate over with a for-in loop, which suggest object. And for me it begs the question, why aren't you storing this in an array? Because if you were, your code could just be this.
jQuery('<li></li>').click(function()
{
$(this).addClass( classes.join( ' ' ) );
});
Your code:
for(class_id in classes) {
callback = function() { this.selectClass(class_id) };
li_item = jQuery('<li></li>')
.click(callback);
}
This is mostly ok, just one problem. The variable callback is global; so every time you loop, you are overwriting it. Put the var keyword in front of it to scope it locally and you should be fine.
EDIT for comments: It might not be global as you say, but it's outside the scope of the for-loop. So the variable is the same reference each time round the loop. Putting var in the loop scopes it to the loop, making a new reference each time.
This is a better cleaner way of doing what you want.
Add the class_id info onto the element using .data().
Then use .live() to add a click handler to all the new elements, this avoids having x * click functions.
for(class_id in classes) {
li_item = jQuery('<li></li>').data('class_id', class_id).addClass('someClass');
}
//setup click handler on new li's
$('li.someClass').live('click', myFunction )
function myFunction(){
//get class_id
var classId = $(this).data('class_id');
//do something
}
My javascript fu is pretty weak but as I understand it closures reference local variables on the stack (and that stack frame is passed around with the function, again, very sketchy). Your example indeed doesn't work because each function keeps a reference to the same variable. Try instead creating a different function that creates the closure i.e.:
function createClosure(class_id) {
callback = function() { this.selectClass(class_id) };
return callback;
}
and then:
for(class_id in classes) {
callback = createClosure(class_id);
li_item = jQuery('<li></li>').click(callback);
}
It's a bit of a kludge of course, there's probably better ways.
why can't you generate them all and then call something like
$(".li_class").click(function(){ this.whatever() };
EDIT:
If you need to add more classes, just create a string in your loop with all the class names and use that as your selector.
$(".li_class1, .li_class2, etc").click(function(){ this.whatever() };
Or you can attach the class_id to the .data() of those list items.
$("<li />").data("class_id", class_id).click(function(){
alert("This item has class_id "+$(this).data("class_id"));
});
Be careful, though: You're creating the callback function anew for every $("<li />") call. I'm not sure about JavaScript implementation details, but this might be memory expensive.
Instead, you could do
function listItemCallback(){
alert("This item has class_id "+$(this).data("class_id"));
}
$("<li />").data("class_id", class_id).click(listItemCallback);

JavaScript mechanism for holding onto a value from a user action

I've created a JavaScript object to hold onto a value set by a user checking a checbox in a ColorBox.
I am relatively new to jQuery and programming JavaScript "the right way" and wanted to be sure that the below mechanism for capturing the users check action was a best practice for JavaScript in general. Further, since I am employing jQuery is there a simpler method to hold onto their action that I should be utilizing?
function Check() {
this.Checked = false;
}
obj = new Check;
$(document).ready(function() {
$('.cboxelement').colorbox({ html: '<input id="inactivate" type="checkbox" name="inactivatemachine"> <label for="inactivate">Inactivate Machine</label>' });
$(document).bind('cbox_cleanup', function() {
obj.Checked = $.fn.colorbox.getContent().children('#inactivate').is(':checked');
});
$(document).bind('cbox_closed', function() {
if ($($.fn.colorbox.element()).attr('id').match('Remove') && obj.Checked) {
var row = $($.fn.colorbox.element()).parents('tr');
row.fadeOut(1000, function() {
row.remove();
});
}
});
});
Personally, I would attach the value(s) to an object directly using jQuery's built-in data() method. I'm not really entirely sure what you are trying to do but, you can, for instance, attach values to a "namespace" in the DOM for use later one.
$('body').data('colorbox.checked',true);
Then you would retrieve the value later by:
var isChecked = $('body').data('colorbox.checked');
You run the data() method on any jquery object. I would say this is best-practice as far as jQuery goes.
You could capture the reference in a closure, which avoids global data and makes it easier to have multiple Checks. However, in this case it appears to be binding to the single colorbox, so I don't know that you could usefully have multiple instances.
function Check() {
this.Checked = false;
var obj = this; // 'this' doesn't get preserved in closures
$(document).ready(function() {
... as before
)};
}
var check = new Check; // Still need to store a reference somewhere.
$($.fn.colorbox.element()) is redundant. $.fn.colorbox.element() is already a jquery element.
It's common use (in the examples i watched, at least) to prepend a $ to variables referencing jquery elements.
So, var $rows = $.fn.colorbox.element().parents('tr'); gives instantly the idea that it is referencing jquery element(s).
I am afraid fadeOut won't work on rows in IE6 (if i recall correctly). You should be able to hide all the content inside the <tr> before removing it.
Can't help on the "simplify" thing because i don't know the colorbox's best uses.

Categories