Playing with some jQuery bits and pieces, I can't seem to get it quite right.
What I'm looking to do is loop through the items on a page, and apply a custom click handler to them. This way when I populate the page I just need to fill out the correct ID and the jQuery can automate filling in the link.
The problem I have is both events are firing with the same output, and I can't work out why. I have 2 which I am using as buttons,
<img id="LightButton11Of" src="images/lightbulb_off.png" style="width:32px;
height:32px; vertical-align:middle " alt="off" >
<img id="LightButton11On" src="images/lightbulb_on.png" style="width:32px;
height:32px; vertical-align:middle" alt="on">
and the following code
$(document).ready(function(){
$("[id^=LightButton]").each(function(i,item){
if ($(this).attr('id').substr(13,2)=="On"){lightfunction="1";}
if ($(this).attr('id').substr(13,2)=="Of"){lightfunction="0";}
alert($(this).attr('id').substr(13,2));
numLight = $(this).attr('id').substr(11,2);
strLight = "*1*" + lightfunction + "*" + numLight + "##";
teststr = $(item).attr('id') + " - " + strLight;
alert(teststr);
$(this).bind("click",function(){
SendEvent("OWN","18",strLight,"OK");
});
});
});
The alert(teststr) gives exactly the output I'm expecting, but it seems when I'm binding it is actually binding to all elements and not just the singular item from that iteration of the each loop.
What am I doing wrong!?
Cheers,
Tim.
What am I doing wrong!?
Your variables are all global.
This code in the click handler:
SendEvent("OWN","18",strLight,"OK");
...refers to the global variable strLight, which will have the value set in the last iteration of your .each() loop.
You should declare all of your variables with var, which will make them local to the function, so then each of the click handlers will use the local variable from its containing scope. (Which, thanks to the "magic" of closures, will still exist even though your each callback will have finished by the time the click events occur.)
$("[id^=LightButton]").each(function(i,item){
var lightfunction = this.id.substr(13,2)=="On" ? "1" : "0";
var numLight = this.id.substr(11,2);
var strLight = "*1*" + lightfunction + "*" + numLight + "##";
$(this).bind("click",function(){
SendEvent("OWN","18",strLight,"OK");
});
});
(Note also that there's no need to use $(this).attr('id') when this.id gives you the same value in a way that is faster to type, read and execute, and it's neater to set the value of lightfunction using a ternary operator instead of two if statements.)
Related
I am running into a problem people have posted before: JavaScript dynamic parameters
But my code uses nodes rather than innerHTML assignments, so the existing SO post doesn't seem to apply to my code.
I want to dynamically generate HTML buttons in a table. For each button, I want to call a function with parameters that depend on the button's index/position in the table. First I tried just using lambda functions with the variable over which I was incrementing. This didn't work, so I also tried dynamically named variables, meaning each button should be passing a differently named variable to deal with lazy-loading effects. But this didn't work either. You can see both versions of what I tried in the code below:
This code I paste below is in a for-loop. In the following, I increase i by 1 each time. offset and jj are unchanged within the loop.
var variableDynamic = i.toString();
window['variableName' + variableDynamic] = i + offset;
upvote.onclick = function() {
upVoteA(i + offset, jj);
//upVoteA(window['variableName' + variableDynamic] , jj);
};
upvote.innerHTML = "Upvote"
Someone told me to look into closures, so following this recommendation: http://www.usepatterns.com/blog/javascript-closures I rewrote the onclick function declaration as:
upvote.onclick = function() {
var a = i + offset;
var b = kk;
function closeIt(){
upVoteA(a,b);
}
closeIt();
};
This still has the same effect that every button calls upVoteA with the same parameter, namely the last/highest value.
I realize I could refactor my code to turn these into .innerHTML set statements and then I'd print the variable and it would be set as a printed statement instead of a lazily-loaded variable. But I'd like not to do that if possible. (apologies that it's not technically lazy loading, the name seems reasonably apt)
Is there a way to make my code work? And why are the closures failing? I thought closures should preserve the environment they were created in, but that is not the case with my code. Which portion of the "environment" are preserved with closures?
This is a very bad answer, but it works
var funcStr = "function dummy() { upVoteA(" + (i + offset) + "," + jj + "); }";
eval(funcStr);
upvote.onclick = dummy;
But if you have something better, please let me know.
I have a function that uses each to go over each element in a set and renumber them after one is removed from the DOM.
Right now that function looks like this:
renumber_items = function(){
$(".item_set").each(function(index){
$(this).find('legend').html("Item " + (index+1));
});
};
I remember reading somewhere that find is a really inefficient operation, so I was wondering if there's a way to combine the 'legend' selector into a compound selector with this.
If there is only one legend per .item_set this will abbreviate things a bit:
renumber_items = function(){
$(".item_set legend").html(function(index){
return "Item " + (index+1);
});
};
.html can take a function and the result is stored.
If there is more than one legend per .item_set you will need to retain an outer each to keep the numbers sequential for each set.
Generally if you have speed issues, on a function called many times, and the jQuery selector result is on a fixed set of elements, you just archive the search to a variable once at page load and reuse that:
var $legends = $(".item_set legend");
renumber_items = function(){
$legends.html(function(index){
return "Item " + (index+1);
});
};
Maybe you can try with .filter(). As others say, it shouldn't be such a performance issue as long as you're not using it all the time. Consider labeling all the items you want to find/filter, so that you can get them all in one JQuery selector at once, and you don't have to go filtering everything after. Else you can use (as commented out by #Regent):
renumber_items = function(){
$(".item_set legend").each(function(index){
$(this).html("Item " + (index+1));
});
};
You can replace:
$(this).find('legend')
with:
$('legend', this)
The second argument sets the context in which jQuery searches for 'legend'.
If it is omitted, the context defaults to be document.
Creating a JavaScript global array with static elements?
The problem isn't that removeFunction doesn't have access to bigArray. The problem is in your onclick attribute, and the id you're putting on the link:
$('#div').append("<a href='#' id='bigArray[i]' onclick='removeFunction(bigArray[i])'>Element bigArray[i]</a><br />");
In the onclick, you're referring to i, but A) I'm guessing i isn't a global, and B) Even if it is, it will not have the value of i that you used to render that row. The code will look for the value of a global i variable as of when the link is clicked.
Separately, you're creating multiple elements with the same id value, which is bigArray[i] (not bigArray[0], bigArray[1], etc.)
You could use the value instead, like this:
$('#div').append("<a href='#' id='bigArray[" + i + "]' onclick='removeFunction(" + i + ")'>Element bigArray[i]</a><br />");
The changes there are:
For the id, I changed it to: "...id='bigArray[" + i + "]'", which will output id='bigArray[0]', then id='bigArray[1]', etc., instead of repeatedly outputting id='bigArray[i]' (literally.
I just pass the index into removeFunction, again by putting the value there, not a reference to the variable i: "... onclick='removeFunction(" + i + ")' ..."
Then your removeFunction would be:
function removeFunction(i) { // <== i, not id
bigArray.splice(i, 1); // <== real code, not pseudocode
renderArray(bigArray);
}
I would not recommend doing it that way, but it's the minimal fix.
There's no need to pass bigArray to anything. It's a global.
FWIW, I would recommend refactoring so you don't have to re-render the whole thing every time.
Define a variable at the global scope first that will hold your "bigArray", then assign the value to it once you receive the data through your ajax call.
var bigArray;
$.ajax({
bigArray = bigArrayFromAjax;
renderArray(bigArray);
});
... then your other functions should have access to it.
What is the best practice to check if a DOM element exists in javascript?
Should one check if an item exists prior to using it, like so?
if ($("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "").size() != 0) {
var row = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
}
wouldn't this execute packageId.removeSpecialChars().toUpperCase() twice?
OR would this be better option?
var row = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
if (row)
{
// do something
}
However, wouldn't it throw an exception when not found?
When you're actually working with DOM elements, then yes, you should check that it exists before you attempt to work with it to avoid JavaScript errors. However, you're not working with DOM elements, you're working with a jQuery object that (potentially) contains DOM elements.
jQuery functions already handle cases where there are no matches in its set of elements, so you don't need to explicitly check for yourself that there are elements before attempting to work with them. You'd only need to do so if you're trying to directly reference DOM elements from inside that set, using the .get() function or the [index] square bracket notation.
As an aside, the .size() jQuery function was deprecated in version 1.8, you should use the jQuery object's length property directly to check if there are elements, so:
var $object = $('a-selector');
if($object.length) {
// there's at least one matching element
}
Better to cache it:
var machId = $("#" + machineId + packageId.removeSpecialChars().toUpperCase());
if (machId.size() != 0) {
var row = machId;
}
General programming conventions say don't repeat yourself. So, in this case, you could at least do the finding of the thing only once and keep a variable reference:
var thing = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
Then the selection lookup doesn't happen twice, redundant method calls removed etc. This also has the benefit of allowing you to write more self-explanatory code, when you can name a variable to something meaningful, other than thing, or, eeek!, a (though it isn't necessarily so that code must be more meaningful, people still use names like a!)
if (thing != null) { }
if (thing.size() != 0) {
}
etc.
As for calling methods multiple times, that's often unavoidable.
It does, what you need is:
var a = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
if (a.size()) {
var row = a;
}
You basically need to see if the DOM element is exist in your HTML, but beer in mind that jQuery doesn't throw a fatal error if the element not exist in your DOM, but would a good practice to check, it adds one more secure layer on your code, there was something called .size() that deprecated from version 1.8, so not recommended to use even you use old version of jQuery, so the best solution at the moment would be something like below code:
if($('.class').length) { // check if any element with this class exist, if not exist, it return 0 and can not pass the if estatement
// do something
}
I'm experiencing a problem when I attempt to use the .change() event on select lists, using the jQuery .toChecklist plugin.
My page contains a number of select lists, which are changed to CheckLists, using jQuery.
Consider the following Javascript snippet:
for (var i=0;i<5;i++)
{
var selectListId = 'selectList' + i;
// Assume this line represents the outputting on a
// standard select list
// Convert to Checklist
$("#" + selectListId).toChecklist();
$("#" + selectListId).change
(
function ()
{
alert("SelectListId: " + selectListId);
}
);
}
For each iteration of this loop, I output a multi-select list, convert it to Checklist, and then add the .change() handler.
However, when the page renders (visually, everything is fine), choosing an option from ANY of the lists gives the alert text of "SelectListId: selectList4" (ie. the last list id of the loop). Thus it appears that each invocation of .change() globally replaces the change handler.
Does anyone know how to change the code so that each checklist has its own change handler (so that the first change handler would output "SelectListId: selectList0", etc).
Thanks,
Try pulling the change function out of the loop. I also added a line that adds a class to each list. The new change function references the lists by the class and will know which is actively being changed via this.
for (var i = 0; i < 5; i++) {
var selectListId = 'selectList' + i;
$("#" + selectListId).toChecklist();
$("#" + selectListId).addClass('newChecklist');
}
$('.newChecklist').change(function() {
alert( $(this).attr('id') );
});
So, after a lot of head scratching, I've found a work-around for this issue.
While concatenating strings together in the anonymous function behaves in an unexpected manner, quoting the whole line of code and wrapping it in an eval statement produces the required results.
Thus, instead of writing, as above:
$("#" + selectListId).change
(
function ()
{
alert("SelectListId: " + selectListId);
}
)
You would need to write this instead:
eval('$("#' + selectListId + '").change(function (){alert("SelectListId: ' + selectListId + '");});');
This may not be the best approach, but it works, and for now that's good enough! :-)