Accessing Parameters *and* Events in function from jQuery Event - javascript

This is a follow-up question to a different question I asked not too long ago. Typically, you can access an event in a function call from a jQuery event like this:
$item.live("click", functionToCall);
and in the function:
function functionToCall(ev) {
// do something with ev here, like check 'ev.target'
}
But what if I wanted to send a parameter to functionToCall() and access the event? So something like this, maybe? :
$item.live("click", functionToCall($(this)); // send over parameter this time
and
function functionToCall(ev, $clickedItem) {
// both accessible here?
alert(ev.type);
alert($clickedItem.attr('id'));
}
Would that be acceptable, or is there a different way to send a parameter? Because this way doesn't seem right to me. Any help would be appreciated. Thanks.
CLARIFICATION: I realize that an anonymous callback function would allow me to access both, but for various reasons too lengthy to get into in this post, I need to use a function call rather than the anonymous function. So my question deals strictly with the scenario when an external function needs to be called. Thanks.
UPDATE: My original question presented the scenario of needing to pass $(this) as a parameter to the external function. As it turns out, $(this) will be accessible in the function without even needing to pass it, because of the way jQuery reassigns values to "this" based on events. So performing this code should work for my original question:
$item.live("click", functionToCall);
and
function functionToCall(ev) {
alert(ev.type);
alert($(this).attr('id')); // display id of item that was clicked
}
However, as others have answered, there is a different scenario that involves needing to pass a different kind of variable over as a parameter, such as a simply string or int. In this case, as others have notes, it becomes more complicated. But there do seem to be sufficient answers here to satisfy this second scenario (namely, "currying"). Thanks.

You can curry or partially apply your function:
Something like this:
function functionToCall($clickedItem) {
return function (ev) {
// both accessible here
alert(ev.type);
alert($clickedItem.attr('id'));
}
}
Then you can use it like you want:
$item.live("click", functionToCall($(this));
Note: If you can't modify your original functionToCall because is "external", you can wrap it:
function wrapFunctionToCall($clickedItem) {
return function (ev) {
// call original function:
functionToCall(ev, $clickedItem);
}
}
// ...
$item.live("click", wrapFunctionToCall($(this));

.live('click', functionA(prm) {
return function(ev) {
// here you can use the prm and the event
}
}

I know the question's answered, but for what it's worth, here's a one-liner that works as well if you don't want to edit your original function(s):
$item.live("click", function() { functionToCall( $(this) ); });

Related

What is the right way to pass parameters into callback functions?

It's a working example. As you can see I'm calling setElemHeight function with a returned value from getElemHeight. But
i think it's a bad practice to pass value instead of function.
Option 1) I can wrap it in anonymous function like this:
function () { getElemHeight('.map-svg') }
Option 2) I can call it inside setElemHeight function and pass parameter there, but i think it would ruin Open Closed Principle.
My question is: What is the right way to pass parameters into callback functions?
Thanks.
function(elem) {
return $(elem).height()
}
function setElemHeight(elem, callback, offset) {
let elemHeight = callback
$(elem).height(elemHeight - offset)
}
$(window).on('load resize', function() {
setElemHeight('#dealersTabContent', getElemHeight('.map-svg'), 190)
})
Passing a value to a function is just fine, unless the height of the element changes over time.
If it changes, you'll want to re-calculate the height on each call of the event handler. If it does not, you can just pass the value in, since it will always use the same value anyway.
Also, this line:
let elemHeight = callback
If callback is a function, this doesn't actually invoke the callback, it just assigns the function to a new variable.
If you want elemHeight to get the return value, you'll need to do this instead:
let elemHeight = callback() // <== Notice the parenthesis
If it's a value, you probably want to change the name.
As for the Open Closed principle, I think that in JS, being a dynamic, weakly typed language, this will probably mostly apply to modules and not a specific code snippet. There are some tricks for creating encapsulation in JS and for allowing extension only in certain places, but generally you can plug anything you want anywhere, so I just don't worry about it.

JQuery: What is a proper way to keep the scope of an object when different functions are called?

I have a general question as the title states. I am just not sure if what I am doing is the proper way or perhaps there is an easier way to keep the scope of an object.
I have here an example just to illustrate what I am working with. You can see I am passing the "this" object to the function via a parameter however is this the only way to do it? Is there a way I don't need to pass the parameter.
Please note that the criteria is there is no identifying class/ids on the forms.
http://jsfiddle.net/TmaHs/
Thanks.
As per your example, there are easier ways :
$("form select").on('change', changeText);
function changeText(e) { //the event is still available
$("input", e.target.form).val("Changed!"); //so is the target and the form
}
will do the same. FIDDLE
As for more complex functions, passing an element to the function is usually not a problem, and in my opinion it's better than using globals.
You can use the standard bind function to explicitly set the context
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Or the similar equivalent in jquery if it has one (I'm not sure about this)
That's a valid way of doing it - an alternative is to declare the variable in a higher scope, assign it in the first function, and reference it without the need to use arguments in the second function.
See this edit to your JSFiddle for example.
You could work with the window object, by declaring the variable out of score, then setting the window-object of it.
$("form select").change(function() {
window.thisObj = $(this).parent("form");
changeText();
});
function changeText() {
window.thisObj.find("input").val("Changed!");
}
​
But the way you did it is the proper one, working with a window-object should always be avoided.
I don't see anything wrong in that approach... Its perfectly fine :)
BUT, if you still want to know the other way, then there are many...
I will post couple of them over here...
1) Retrieve all the values that you need in your selector function, and pass them to the function.
$("form select").change(function() {
var val1 = $("selector1").val();
var val1 = $("selector2").val();
var val1 = $("selector3").val();
changeText(val1,val2,val3);
});
function changeText(val1,val2,val3) {
// your operation
}
2) Add the required values to an array, and pass that array to your target function.
var targetArray = new Array();
$("form select").change(function() {
targetArray[0]=//your val1
targetArray[1]=//your val2
targetArray[2]=//your val3
changeText(targetArray);
});
function changeText(arr) {
// your operation
}
​

javascript: assign function parameter if functioncall is a string?

Following example - the YoutubePlayer API
videoID.addEventListener('onStateChange', 'foo');
In the documentation they say, video.addEventListener(string: event, string: function).
That means the first parameter is the event (in my case onStateChange) and the second parameter is the function that is getting called when the even is triggered.
This youtube sample is just a good example, I've alredy had this question a few times before.
If the function to call is passed as a string, is there any chance to assign a a parameter to that function?
Imagine the function I want to call looks like this.
function foo(something) {
console.log(something).
}
It's obviously not possible to add a parameter to the function call is it? Like…
videoID.addEventListener('onStateChange', 'foo(videoID)');
Thank you for your information and answers.
You could do something like:
videoID.addEventListener('onStateChange', function()
{
foo(videoID);
});
...if I'm understanding you correctly.

Using jQuery in a JavaScript function

function divlightbox(val)
{
if(val)
{
val=val.replace( /^\s+/g, "" );
var count_js=0;
var big_string='';
document.getElementById("video_lightbox").innerHTML="";
document.getElementById("divlightbox").style.display = "block";
$("#video_lightbox").css({"height":"430px","top":"10%","width":"480px"});
I found out that the error is in the above. My question is can't I use jQuery and traditional JavaScript at same time? I have done coding like this numerous times and never ran into a problem like this. I used to use jQuery methods like .hide() and .css() inside JavaScript functions but this time it doesn't work.
Thanks in advance.
While the other answers fix the specific problems, I don't think the OP's question (in bold) is really answered here, as depending on the specific context, $ may possibly not be defined as a jQuery object yet (having had this problem myself a few times now.)
In which case you would need to do something like:
function divlightbox(val) {
// ...
// just use jQuery instead of $ one time
jQuery("#video_lightbox").css({"height":"430px","top":"10%","width":"480px"});
}
OR
function divlightbox(val) {
// define the $ as jQuery for multiple uses
jQuery(function($) {
// ...
$("#video_lightbox").css("height":"430px");
$("#video_lightbox").css("top":"10%");
$("#video_lightbox").css("width":"480px");
});
}
jQuery is JavaScript so YES. Instead .innerHTML="" just use .empty(). Instead .getElementById() use $('#..') and so on.
to do things like hide(); and css() you need jquery objects. you can't do them to dom elements.
so you could do $('#video_lightbox').html("");
or
$('#video_lightbox').empty();
You must provide error in javascript console.
1) Do you pass a val argument to divlightbox function()? When do you call it?
2) why do you use the same identifier divlightbox both for a function and for a div id? Change name to the function please, maybe the problem could be here.
3) Always check if video_lightbox and divlightbox exist before accessing them.

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);

Categories