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! :-)
Related
I have dynamically created elements on the page, a picture and three buttons which are created upon clicking the main button.
All of this works, but now I am trying to change the display on the dynamically created div with the pics to "none".
More than one issue arises here for me, first I cannot find out how to make the div "images" the target, or select it.
I am trying to get one function to do this for all the elements, they are all structured equally just the pictures are different.
function hidePic(arrayPos){
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
document.getElementsByClassName("closingButton")[0].addEventListener("click", function(){
hidePic(0);
});
This is the relevant code, lines 4 to 10. If this is commented out, the rest of the code works, but as it is I get entirely unrelated errors in dev Tools.
Click this link to see Codepen.
So the question is, how can I best implement the above code?
So just working on the code above you can do this in order to make it work for all instances. First let me point out that this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]";
will never work. That line is building a string. What you really want to make that line work is:
var elem = document.getElementsByClassName("closingButton")[arrayPos];
But even that I find unnecessary. Take a look at this code.
function hidePic (elem) {
var finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
var closingButtons = document.getElementsByClassName("closingButton");
var index = 0, length = closingButtons.length;
for ( ; index < length; index++) {
closingButtons[index].addEventListener("click",
function () {
hidePic(this);
}
);
}
This first finds all elements with the class closingButton. Then for each one we attach a click event listener. Instead of attempting to pass some index to this hidePic function we already have our function context which is what you seem to be trying to find in the function so lets just pass that and use it to find the image inside.
Let me know if you have any questions. I took a look at your codepen as well. I am not sure you should be forcing all that interactive HTML into a button element honestly, which itself is considered an interactive element. Not sure that meets the HTML spec. Perhaps add that HTML below the button. I bet when you click on things inside of that button it will register as clicks on the button as well unless you remove the event upon inserting your elements but then it seems like its getting too complicated for the simple stuff you are trying to do here.
The codepen complains because there is no element with the "closingButton" class, so it's trying to call addEventListener on nothing, but I'm doubting that's the actual error you're seeing.
It's also worth nothing that I think this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
is excessive.
var elem = document.getElementsByClassName("closingButton")[arrayPos];
should be sufficient. Also not the syntax error at the end of the same line: it should be ; not ,. If this is the error in your code it could explain why you were getting "unrelated errors" syntax errors can cause misleading problems that are supposedly in other areas of the code!
Lastly, I'd highly recommend using JQuery to do your selection magic - it's exactly what it was designed for. If you're averse to using JS libraries, fair enough, but it would make your code a lot simpler and you can have reasonable confidence that it will perform the tasks about as optimally as is possible.
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'm using jquery's "data" function to store some information associated with an HTML element. When this element is explicitly referenced throughout the function, the function returns undefined, however when I bind the element to a variable the function works just fine.
Please check out this example below:
This one works (http://jsfiddle.net/sdbA5/):
$(function() {
var div = $("#officerBob");
jQuery.data( div, {
arrests: 16
});
div.click(function(){
alert("Bob has " + jQuery.data( div ).arrests + " arrests");
});
});
and this one doesn't (http://jsfiddle.net/5Y6b8/1/):
$(function() {
//var div = $("#officerBob");
jQuery.data( $("#officerBob"), {
arrests: 16
});
$("#officerBob").click(function(){
alert("Bob has " + jQuery.data( $("#officerBob") ).arrests + " arrests");
});
});
jQuery.data() is supposed to be working off DOM nodes and not jQuery collections.
Changing your code to use a DOM node and it will start working
$(function() {
jQuery.data( $("#officerBob")[0], {
arrests: 16
});
$("#officerBob").click(function(){
alert("Bob has " + jQuery.data( $("#officerBob")[0] ).arrests + " arrests");
alert("Bob has " + jQuery.data( this ).arrests + " arrests");
});
});
Now why would the first one work? Because it is working off the same object. In the second one, it is a new jQuery object which is not the same as the first one that was assigned.
I'm not exactly sure why this is so, but all of the jQuery documentation shows the jQuery objects written as variables before they are passed as parameters in functions.
See: http://learn.jquery.com/using-jquery-core/jquery-object/
I'm just learning more about JavaScript, but I have a feeling that the jQuery object itself $("#officerBob") is essentially a function. Your code, which utilizes the output of that function, is not expecting a function as an input. It is expecting an object.
So, once you encapsulate the output as an object, (by creating an object whose value is equal to the function's output) your code works.
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 having a bit of a difficulty using jQuery to bind a function to an element. Basically I have a set of divs that I load using JSON. Each of the JSON items have an associated "action" that defines what function to call if that div is clicked.
As I iterate through each of the JSON items, I do (editied for clarity):
for (var i = 0; i < JSONOBJECT.length; i++) {
var divbox = $('<div id="' + i + '"><img /></div>');
var action = JSONOBJECT[i]["action"];
$(divbox).click(function () {
eval(action); // bind function
});
$('.navigationarea').append(divbox); // Append to area
}
So for example, JSONOBJECT[0]["action"] can contain a "doThis()", while JSONOBJECT[1]["action"] can contain a "doThis2()".
The problem is that action always ends up being the action associated with the last item in the JSON object. I know this is an issue related to localizing the variable (or making a copy?) but I'm missing it.
Can I get some help?
Thanks in advance.
Your problem is that the action variable is shared by all of the anonymous methods.
In your specific case, the best solution is to write divbox.click(new Function(action)).
This will also be faster because the code will only be parsed once, not once per click.
In general, the solution is to put the code containing the anonymous function into a separate function, and pass the variable to the separate function as a parameter.
EDIT: Your code can be rewritten like this:
for (var i = 0; i < JSONOBJECT.length; i++) {
$('<div id="' + i + '"><img /></div>')
.click(new Function(JSONOBJECT[i].action))
.appendTo($('.navigationarea'));
}