Dynamically making a Javascript array from loop - javascript

I know there are a lot of questions about this, but I can't find the solution to my problem and have been on it for a while now. I have two sets of input fields with the same name, one for product codes, and one for product names. These input fields can be taken away and added to the DOM by the user so there can be multiple:
Here is what I have so far, although this saves it so there all the codes are in one array, and all the names in another:
var updatedContent = [];
var varCode = {};
var varName = {};
$('.productVariationWrap.edit input[name="varVariationCode[]"]')
.each(function(i, vali){
varCode[i] = $(this).val();
});
$('.productVariationWrap.edit input[name="varVariationName[]"]')
.each(function(i1, vali1){
varName[i1] = $(this).val();
});
updatedContent.push(varCode);
updatedContent.push(varName);
I am trying to get it so the name and code go into the same array. i.e. the code is the key of the K = V pair?
Basically so I can loop through a final array and have the code and associated name easily accessible.
I can do this in PHP quite easily but no luck in javascript.
EDIT
I want the array to look like:
[
[code1, name1],
[code2, name2],
[code3, name3]
];
So after I can do a loop and for each of the arrays inside the master array, I can do something with the key (code1 for example) and the associated value (name1 for example). Does this make sense? Its kind of like a multi-dimensional array, although some people may argue against the validity of that statement when it comes to Javascript.

I think it's better for you to create an object that way you can access the key/value pairs later without having to loop if you don't want to:
var $codes = $('.productVariationWrap.edit input[name="varVariationCode[]"]'),
$names = $('.productVariationWrap.edit input[name="varVariationName[]"]'),
updatedContent = {};
for (var i = 0, il = $codes.length; i < il; i++) {
updatedContent[$codes.get(i).value] = $names.get(i).value;
}
Now for example, updatedContent.code1 == name1, and you can loop through the object if you want:
for (var k in updatedContent) {
// k == code
// updatedContent[k] == name
}

Using two loops is probably not optimal. It would be better to use a single loop that collected all the items, whether code or name, and then assembled them together.
Another issue: your selectors look a little funny to me. You said that there can be multiple of these controls in the page, but it is not correct for controls to have duplicate names unless they are mutually exclusive radio buttons/checkboxes--unless each pair of inputs is inside its own ancestor <form>? More detail on this would help me provide a better answer.
And a note: in your code you instantiated the varCode and varName variables as objects {}, but then use them like arrays []. Is that what you intended? When I first answered you, i was distracted by the "final output should look like this array" and missed that you wanted key = value pairs in an object. If you really meant what you said about the final result being nested arrays, then, the smallest modification you could make to your code to make it work as is would look like this:
var updatedContent = [];
$('.productVariationWrap.edit input[name="varVariationCode[]"]')
.each(function(i, vali){
updatedContent[i] = [$(this).val()]; //make it an array
});
$('.productVariationWrap.edit input[name="varVariationName[]"]')
.each(function(i1, vali1){
updatedContent[i1].push($(this).val()); //push 2nd value into the array
});
But since you wanted your Code to be unique indexes into the Name values, then we need to use an object instead of an array, with the Code the key the the Name the value:
var updatedContent = {},
w = $('.productVariationWrap.edit'),
codes = w.find('input[name="varVariationCode[]"]'),
names = w.find('input[name="varVariationName[]"]');
for (var i = codes.length - 1; i >= 0; i -= 1) {
updatedContent[codes.get(i).val()] = names.get(i).val();
});
And please note that this will produce an object, and the notation will look like this:
{
'code1': 'name1',
'code2': 'name2',
'code3': 'name3'
};
Now you can use the updatedContent object like so:
var code;
for (code in updatedContent) {
console.log(code, updatedContent[code]); //each code and name pair
}
Last of all, it seems a little brittle to rely on the Code and Name inputs to be returned in the separate jQuery objects in the same order. Some way to be sure you are correlating the right Code with the right Name seems important to me--even if the way you're doing it now works correctly, who's to say a future revision to the page layout wouldn't break something? I simply prefer explicit correlation instead of relying on page element order, but you may not feel the need for such surety.

I don't like the way to solve it with two loops
var updatedContent = []
$('.productVariationWrap.edit').each(function(i, vali){
var $this = $(this)
, tuple = [$this.find('input[name="varVariationCode[]"]').val()
, $this.find('input[name="varVariationName[]"]').val()]
updatedContent.push(tuple)
});

Related

Storing data with nested .each() function causes to replicate values

I've modified a survey-multi-choice plugin from JsPsych, in order to get responses in the form of checkboxes, instead of radio-buttons, since I need to present an image to the user, followed by 4 alternatives, like this:
Where A, B, C and D are also images, with their respective checkbox below each one. This structure must be presented more than once, like this:
So in this example, the expected output would be:
{"Q0":["Option A","Option B"]} //this is the first "question" displayed
{"Q1":["Option B","Option C"]} //second one
{"Q2":["Option B","Option D"]} //third one
But instead I get the first answer replicated for the rest of the questions:
{"Q0":["Option A","Option B"]} //this is the first "question" displayed
{"Q1":["Option A","Option B"]} //second one
{"Q2":["Option A","Option B"]} //third one
My code is provided below:
$("div." + plugin_id_name + "-question").each(function(index) {
var id = "Q" + index;
var val = [];
var a = $(".jspsych-survey-multi-choicemrbeta-option");
$("input:checkbox:checked").each(function(){
val.push($(this).attr("value"));
});
var obje = {};
obje[id] = val;
$.extend(question_data, obje);
});
I've tried tracking down the values generated on each step by printing them on console, so I'm guessing the problem is how I'm implementing those nested loops, thus the name of this question.
I've tried different approaches when implementing this loop, without better results:
for (var j = 0; j < trial.options[index].length; j++) {
if ($('jspsych-survey-multi-choicemrbeta-response-' + j).is(':checked')) {
val.push($(this).attr("value"));
}
}
A working example of my full code can be found here, for you to test it (look for the jspsych-survey-multi-choicemrbeta.js file, from line #141). CSS isn't included so it'll look slightly different.
Please note that the output of this code is a CSV file, and the full set of responses is given on a single cell, since all those questions belongs to the same JsPsych "trial".
Thanks for your help.
The inner loop iterates over all checkboxes, not only the ones belonging to the question.
Assuming the checkboxes are descendants of the div for the associated question, you should change the inner loop from this:
$("input:checkbox:checked").each( ...
to this:
$(this).find("input:checkbox:checked").each( ...

Matching Array to JavaScript Matrix

I’m wondering how to solve a matching/lookup problem and I “think” a multi-dimensional array is the solution. In short, I want to match a list of comma separated SKUs stored as a cookie value against a finite list of SKUs with matching product names and print out the matched product names onto the page. I’m not sure if this is the best way to do this, but with what I have so far I’m not clear how to properly breakup the comma separated strings from the cookie (right now it’s trying to match the entire cookie value), match them to the matrix (17 total rows) and then print out the Product Name.
<script>
var staticList = [
[“1234”, “Chocolate Ice Cream”],
[“1235”, “Peanut Butter Cookie”],
[“6G2Y”, “Raspberry Jell-O”],
[“YY23”, “Vanilla Wafers”]
];
var cookieSkus = [‘1235,YY23’]; // comma separated value from cookie
jQuery(function () {
for (var i = 0; i < staticList.length; i++) {
if (cookieSkus.indexOf(staticList [i][0]) > -1) {
jQuery('#pdisplay).append(staticList [i] [1] + '<br />');
}
}
});
</script>
<p id=”pdisplay”></p>
In this example, the paragraph "pdisplay" would contain:
Peanut Butter Cookie
Vanilla Wafers
Is there a way to correct what I have above or is there a better method of accomplishing what I’m trying to do?
First, you might want to focus on the Cookie SKUs rather than the staticList. The reason for this is that the cookie may have a variable number, and may be as small as 0 elements. (After all, we don't need to list the items if there are no items).
This may be accomplished simply by converting the string to an array and then checking if the SKU is in the staticList. Unfortunately, since you are using a multidimensional array, this would require going through the staticList for each cookie sku. Using just this suggestion, here is a basic example and fiddle:
Rewrite: Accounting for the fact that staticList is an Array of Arrays
jQuery(function() {
var skus = cookieSkus[0].split(',');
for (var i = 0; i < skus.length; i++) {
for (var j = 0; j < staticList.length; j++) {
if (staticList[j][0] == skus[i]) {
jQuery('#pdisplay').append(staticList[j][2] + '<br/>');
break; // Will end inner if the item is found... Saves a lot of extra time.
}
}
}
});
Edit 2: Using an Object (A possibly better approach)
According to the comments, you must support IE8. In this case, you might consider an Object instead of a multi-dimensional array. The reasons for this are as follows:
An object is actually an associative array (with a few perks).
You can directly check for property existence without having any nested arrays.
Object property access is typically faster than looping through an array
You can access object properties nearly exactly like accessing an array's elements.
When using an Object, the original version of my code may be used without modification. This is because the object's structure is simpler. Here is a fiddle for you: option 2
var staticList = {
"1234": "Chocolate Ice Cream",
"1235": "Peanut Butter Cookie",
"6G2Y": "Raspberry Jell-O",
"YY23": "Vanilla Wafers"
};
jQuery(function() {
var skus = cookieSkus[0].split(',');
for (var i = 0; i < skus.length; i++) {
if (staticList[skus[i]])
jQuery('#pdisplay').append(staticList[skus[i]] + '<br/>');
}
});
Responding to your comment:
The reason that the output matches what is desired is because unlike an array which has numerical indices, the object's indices are the actual skus. So, there is no staticList[0] if staticList is an object. Instead (in the context of the staticList object), 1234 = "Chocolate Ice Cream". So, an object definition basically goes as follows:
var objectName = {
index1: value1,
index2: value2,
...,
...
}
The index may be any primitive value (integer or string). The value may be any valid javascript value including a function or an inner object. Now, to get the value at a specific index, you may do either:
objectName.index1 (no quotes)
OR:
objectName["index1"] (quotes needed if the index is a string)
The result of either of those will be:
value1
It's as simple as that.
I would try something like this:
var cookieSkus = cookieSkus[0].split(',');
staticList.filter(function(cell){
return cookieSkus.some(function(val){return cell[0] === val; });
}).map(function(cell){
jQuery('#pdisplay).append(cell[1] + '<br />');
});
Disclaimer: provided based on the sample code provided above along with recent comments

Accessing a Dynamically Named array in jQuery/javascript

I wish to name an array according to the table row containing the button that was clicked.
I get the table row thus:
var rowNum = $(this).parent().parent().index();
Now, I wish to name the array and access it.
var arrayName = 'arrTR' + rowNum;
window[arrayName] = new Array();
window[arrayName]["First"] = "Bob";
window[arrayName]["Last"] = "Roberts";
window[arrayName]["email"] = "me#there.com";
//The array should be accessible as arrTR__
alert(arrTR1["Last"]);
The alert does not work, so I am doing something wrong.
How should I refactor the code to allow me to update and access the array?
jsFiddle
What you're doing with the dynamically named variables is essentially creating an array of those variables (one for each rowNum), but giving each of those array elements its own individual named variable.
There is a much better way to do this. Instead of generating a series of dynamically named variables, make a single array or an object. Then add an element or property for each of the dynamically named variables you were going to generate.
Your test code could look like this:
var arrTR = [];
var rowNum = 1;
arrTR[rowNum] = {
First: 'Bob',
Last: 'Roberts',
email: 'me#there.com'
};
alert( arrTR[1].Last );
Alternatively, you can do something with $.data as mentioned in Johan's answer. But if you do use plain JavaScript code, use a single array as described here instead of multiple dynamically named variables.
There are several reasons to do it this way. It's cleaner and easier to understand the code, it may be faster when there are large numbers of entries, and you don't have to pollute the global namespace at all. You can define the var arrTR = []; in any scope that's visible to the other code that uses it.
Arrays and objects are made for keeping track of lists of things, so use them.
There is nothing wrong with your code, and the only place it has error is the alert since it is not defined on the first click button
see this fiddle with a little update
if(rowNum === 1)
alert(arrTR1["Last"]);
else if(rowNum === 2)
alert(arrTR2["Last"]);
fiddle
How about something like this?
$('.getinfo').click(function() {
var result = $('table tr:gt(0)').map(function(k, v){
return {
firstName: $(v).find('.fname').val(),
lastName: $(v).find('.lname').val(),
email: $(v).find('.email').val(),
}
}).get();
//update to show how you use the jQuery cache:
//1. set the value (using the body tag in this example):
$('body').data({ result: result });
//2. fetch it somewhere else:
var res = $('body').data('result');
});
Not sure how you want to handle the first row. I skip in in this case. You can access each row by result[index].
As you might have noticed, this saves all rows for each click. If you want to use the clicked row only, use the this pointer.
http://jsfiddle.net/nwW4h/4/

Better way of splitting and assigning many values in Javascript?

I have a for loop that cycles through the number of elements that the user has created. There are a lot of available settings in this plugin, and each element can receive it's specific settings.
User settings are entered in the following format: speed_x: "1000,500 > 1000,200 > 0,0"
This controls the speed_x in/out for 3 separate elements. The > divides by object and the commas separate the in/out.
So I can grab specific object speed_x values, I've split speed_x into speed_x_set (splitting by >) resulting in:
1 1000,500
2 1000,200
3 0,0`
3 Inside the loop, I grab the value by index (since it's the object #) and split it by comma (to get speed_x_in and speed_x_out.)
for(var i=0; i<OS.numberofobjects; ++i){
OS.speed_x_on_set[i]=speed_x_set[i].split(",")[0],
OS.speed_x_off_set[i]=speed_x_set[i].split(",")[1],
...
};
Everything is assigned by object and by setting in/out correctly into the master OS settings object. T*he problem is I have many, many settings which need to be split in this fashion...* for example: delay_x_set, speed_y_set, opacity_set, etc. Their names are all based on the default setting name, with "_set" added as shown above. Hopefully this provides enough information. Thanks!
I would avoid to access to the same item twice and perform the same split twice for each iteration. So, you could have something like:
for (var i = 0, item; item = speed_x_set[i++];) {
var values = item.split(",");
OS.speed_x_on_set.push(values[0]);
OS.speed_x_off_set.push(values[1]);
}
Notice that in JavaScript 1.7 (Firefox) you can simply have:
for (var i = 0, item; item = speed_x_set[i++];) {
var [on, off] = item.split(",");
OS.speed_x_on_set.push(on);
OS.speed_x_off_set.push(off);
}
And hopefully in the next version of ECMAScript as well.
It's called "destructuring assignment".
I would say to cache the split result
for(var objindex=0; objindex<OS.numberofobjects; ++objindex){
var splits = speed_x_set[objindex].split(","); //Cache the split so its does not need to be done twice
OS.speed_x_on_set[objindex] = splits[0];
OS.speed_x_off_set[objindex] = splits[1];
...
};
What you're looking for is called parallel assignment, but unfortunately, JavaScript doesn't have it.
In ruby, however, it is common to see similar patterns:
first, second = "first second".split
As others have noted, the obvious way would be to cache split results and assign them separately. Sorry for not answering your question directly.

How to reference an array in a function argument

I have a series of arrays that contain words I want to use as text in various HTML divs (there are about 35 of these, I included only a few for brevity).
var bodyplan = ['Anguilliform', 'Compressiform', 'Depressiform', 'Filiform', 'Fusiform', 'Globiform', 'Sagittiform', 'Taeniform'];
var mouthposition = ["Inferior", "Jawless", "Subterminal", "Superior", "Terminal"];
var barbels = ['1', '2', '4 or more'];
var caudalshape = ['Continuous', 'Emarginate', 'Forked', 'Lunate', 'Rounded', 'Truncate'];
I have a switch function that is supposed to change the text based on user selections:
switch(n){
case 1:
changelabels(bodyplan, 8);
break;
case 2:
changelabels(mouthposition, 5);
break;
case 3:
changelabels(barbels, 3);
break;
case 4:
changelabels(caudalshape, 6);
break;
case 5:
changelabels(dorsalspines, 8);
break;
default:
alert("handquestsel error")}};
Finally, I have the function which I would like to make the changes (except it doesn't):
function changelabels(opt1,opt2){
var i = opt2;
var im = opt2 - 1;
var c = 1;
var index = 0;
while (i>=c){
var oldlbl = document.getElementById("rb" + c + "lbl");
var newlbla = opt1.slice(im,i);
var newlblb = opt1.toString();
oldlbl.innerHTML = newlblb;
c = c + 1
index = index + 1
}};
I know the code for my function is just plain wrong at this point, but I have altered it so many times that I'm not sure what's going on anymore. At one point I did have the function able to change the text, but it did so incorrectly (it parsed the name of the array, not extracted a value from the array as I wished). Please help. I know I am overlooking some fundamental concepts here, but am not sure which ones. I've lost count of the hours I've spent trying to figure this out. It's seems like it should be so simple, yet in all my chaotic attempts to make it work, I have yet to stumble on an answer.
EDIT: I want my switch statement to call the function and pass to the function, the appropriate array from which to pull the labels from. The purpose of the app is to help a user learn to identify fish. When the user makes selections on the page, a series of pictures will be shown for various character states with an accompanying label describing the state. For example, when the user selects Mouth Position a series of divs will show the different mouth positions that fish have and have a label below the picture to tell the user what that certain character state is called. I can get the pictures to change just fine, but I am having a hell of a time with the labels.
Why not just something along the lines of:
document.getElementById("bodyplan_label").innerHTML = bodyplan[bodyplan_index];
You seem trying to put everything in really abstract data structures, I see no reason to. Just keep it simple.
Also bodyplan has only 8 elements, so bodyplan[8] will give you an out of bounds exception because arrays start at 0 as is common in all modern programming languages.
If I'm reading your requirement and code correctly, in your switch statement you are passing both a reference to the appropriate array and that array's expected length - you don't need the second parameter because all JavaScript arrays have a .length property.
You don't want to use .slice() to get the individual values out of the array, because that returns a new array copied out of the original - just use arrayVariable[index] to get the individual item at index.
So, putting that together try something like this (with your existing array definitions):
switch(n){
case 1:
changelabels(bodyplan);
break;
case 2:
changelabels(mouthposition);
// etc.
}
function changelabels(data) {
var i,
lbl;
for (i = 0; i < data.length; i++) {
lbl = document.getElementById("rb" + (i+1) + "lbl");
lbl.innerHTML = data[i];
}
}
Notice how much simpler that is than your code? I'm assuming here the elements you are updating have an id in the format "rb1lbl", "rb2lbl", etc, with numbering starting at 1: I'm getting those ids using (i+1) because JavaScript array indexes start at zero. Note also that you don't even need the lbl variable: you could just say document.getElementById("rb" + (i+1) + "lbl").innerHTML = data[i] - however I've left it in so that we have something to expand on below...
Within your function you seem to be changing the labels on a set of elements (radio button labels?), one per value in the array, but you stop when you run out of array items which means any leftover elements will still hold the values from the previous selection (e.g., if the previous selection was "bodyplan" with 8 options and you change to "mouthposition" with only 5 - you probably should hide the 3 leftover elements that would otherwise continue to display the last few "bodyplan" items. One way to do that is instead of setting your loop up based on the array length you could loop over the elements, and if the current element has an index beyond the end of the array hide it, something like this:
function changelabels(data) {
var i,
lbl,
elementCount = 20; // or whatever your element count is
for (i = 0; i < elementCount; i++) {
lbl = document.getElementById("rb" + (i+1) + "lbl");
if (i < data.length) {
lbl.innerHTML = data[i];
lbl.style.display = "";
} else {
lbl.innerHTML = "";
lbl.style.display = "none";
}
}
}
If these elements are labels for radio buttons (just a guess based on the ids) then you'd also want to hide or show the corresponding radio buttons, but I hope you can figure out how to add a couple of lines to the above to do that.
(As mentioned above, be careful about having element ids count up from 1 when the array indexes start at 0.)
If the above doesn't work please post (at least some of) the relevant HTML - obviously I've just had to guess at what it might be like.
SOLUTION: Changed the scope of the array variables to local by moving them into the function where they are used, instead of having them as global variables at the top of the page. I don't understand as I was following every rule of variable declaration. But for some unknown reason, global variables in javascript are abhorrent.
Solution Edit: Found an error in declaring my global variables. This may have been the source of my problem of why I could not access them. But it is a non-issue at this point since I corrected my code.
I don't understand what your trying to achieve exactly with your code. But to pass a variable (in this case an array) by reference you just have to add "&" before the variable.
function the_name(&$var_by_ref, $var_by_value) {
// Here if you modify $var_by_ref this will change the variable passed to the function.
}
More: http://php.net/manual/en/language.references.pass.php
Hope that helps.

Categories