Duplicating JQuery/Javascript functions - javascript

I have a jquery/javascript function that creates an array to be placed in a form's hidden field. However, this is a nested form and so I need to invoke this function many times to populate the hidden field for all the children: test_suite_run[test_runs_attributes][//id][packages_id]. This means that I need to run this function with a different child id each time.
I have added //id to indicate the only differences between the many function calls. I do not know how to duplicate this function without copying it many times manually and replacing //id with the indexes 0...n, for each nested child instance. Could this somehow be done by passing parameters to the javascript function?
Sorry if this a little confusing, I will be happy to explain in more detail if needed.
JQuery Function
$(document).ready(function () {
arr = new Array();
$(document).on('change', 'select[id ^="s_package//id"]', function () {
var arr = $('select[id ^="s_package//id"]').map(function () {
return this.value
})
result = ""
for (j = 0; j < arr.length - 1; j++) {
result += (arr[j] + ", ");
}
result += (arr[arr.length - 1])
$("input[name='test_suite_run[test_runs_attributes][//id][packages_id]']").val(result);
});
});

You can pass an array of ids to use in your function and iterate them:
function somethingMeaningful(ids) {
for (var i = 0, l = ids.length; i < l; i++) {
var id = ids[i];
// do something with this id
}
}
$(function() {
somethingMeaningful(['id1', 'id2', 'idn']);
});
It might also be possible to simplify your selector and calculate the id at runtime, depending on their actual format:
$(document).on('change', 'select[id^="s_package"]', function () {
var id = $(this).attr('id').slice('s_package'.length);
// Do stuff with real id
});

Related

Combining two different JavaScript functions to get JSON sibling data

I have two function.
One- that on 'Click' gets the id value.
second- gets the sibling data from an id.
I want to combine the two functions, so when i click a div, get that 'id' and display the sibling data from JSON
//this returns sibling data from JSON with id=2
const result = characters.find(item => {
// if this returns `true` then the currently
// iterated item is the one found
return item.id === 2
});
console.log(result);
//this allows me to click the different divs to get their id
var divs = document.querySelectorAll(".characterBox");
for(var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', function (event) {
console.log(this.getAttribute("id"));
});
}
You can put the logic directly into the anonymous function and use this.getAttribute("id") instead 2
var divs = document.querySelectorAll(".characterBox");
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', function(event) {
const result = characters.find(item => {
return item.id == this.getAttribute("id")
});
console.log(result);
});
}

Use closure inside array with pure javascript

I need to copy a string inside an array to a value inside another array that is created in a loop. In the end when I print all names are the last in the array of names. I want to copy/clone the value so that I don't have a reference and I would like it to be only in native javascript without external libraries.
This is my code
var exp_names =["name1","name2","name3"];
var i;
for (i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
});
And then all expId are "name3"
Data is loading correctly per file.
I have tried with jquery's extend function and also lodash's clone function, I have tried my own clone function and nothing works it will still throw "name3" for all the expId.
These didn't work:
var newname = new String(exp_names[i]);
var newname = $.extend(true, {}, exp_names[i]);
var newname = $.extend( {}, exp_names[i]);
var newname = _.clone(exp_names[i]);
var newname = exp_names[i].slice(0);
I am desperate by now.
You need to use bind function.
var exp_names =["name1","name2","name3"];
var i;
var func = [];
for (i = 0; i < exp_names.length; i++) {
func[i]=(function(index){
d3.tsv("data/"+exp_names[index], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[index];
});
});
}).bind(this,i);
}
for(i = 0; i < 3; i++){
func[i](i);
}
Another solution is to use let keyword.
ES6 provides the let keyword for this exact circumstance. Instead of using closures, we can just use let to set a loop scope variable.
Please try this:
for (let i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
}
I guess usage of IIFE and bind together, in the first answer is a little weird. It's best to choose either one of them. Since in the newest versions of the browsers bind is way faster than an IIFE closure and the let keyword I might suggest you the bind way.
A similar example to your case might be as folows;
var exp_names = ["name1","name2","name3"],
lib = {doStg: function(d,cb){
cb(d);
}
},
data = [{a:1},{a:2},{a:3}];
for (i = 0; i < exp_names.length; i++) {
lib.doStg(data, function(i,d) {
d.forEach(function(e){
//Do stuff with doStg
e.expId = exp_names[i];
console.log(e);
});
}.bind(null,i));
}

Simple count function does not work

I thought making a simple function where if you click on a button a number will show up inside of a paragraph. And if you continue to click on the button the number inside the paragraph tag will increase. However, I'm getting an error message saying that getElementsByTagName is not a function. Here is the code on jsfiddle, I know there is something simple that I'm doing wrong but I don't know what it is.
HTML
<div class="resist" id="ex1"><h2>Sleep</h2><p></p><button>Resist</button></div>
<div class="resist" id="ex2"><h2>Eat</h2><p></p><button>Resist</button></div>
Javascript
var count = 0;
var resist = document.getElementsByClassName('resist') ;
for(var i = 0; i < resist.length; i++)
{ var a = resist[i];
a.querySelector('button').addEventListener('click', function(a){
count +=1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
}
You are overwriting a variable with event object passed into event handler. Change the name to e maybe, or remove it altogether as you are not using it anyway:
a.querySelector('button').addEventListener('click', function(e /* <--- this guy */) {
count += 1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
Another problem you are going to have is classical closure-in-loop issue. One of the solutions would be to use Array.prototype.forEach instead of for loop:
var count = 0;
var resist = Array.prototype.slice.call(document.getElementsByClassName('resist'));
// ES6: var resist = Array.from(document.getElementsByClassName('resist'));
resist.forEach(function(a) {
a.querySelector('button').addEventListener('click', function(e) {
count += 1;
a.getElementsByTagName('p')[0].innerHTML = count;
});
});
vars in Javascript are function scoped, so you must wrap your event listener binding in a closure function to ensure the variable you're trying to update is correctly set.
(Note: I've renamed a to div in the outer function and removed the arg from the inner click function).
var count = 0;
var resist = document.getElementsByClassName('resist') ;
var div;
for(var i = 0; i < resist.length; i++)
{
div = resist[i];
(function(div){
div.querySelector('button').addEventListener('click', function(){
count +=1;
div.getElementsByTagName('p')[0].innerHTML = count;
});
})(div);
}

How do I build up my Javascript/Jquery for many elements using a loop?

instead of repeating the same code over and over again in my js file with the only difference being the element names, I was hoping to build a loop that would build out my js.
I'm tryign to add toggle functions to some buttons on my page that change their colors and sets a value elsewhere on my page. Here is my code:
var className;
var idName;
var i;
for (i = 0; i < 11; i++) {
className = ".feedbackq";
idName = "#feedbackq";
className = className + i.ToString();
idName = idName + i.ToString();
$(className).toggle(
function () {
$(className).each(function () {
$(className).css("background-color", "");
});
$(this).css("background-color", "red");
var value = $(this).val();
$(idName).val(value);
},
function () {
$(this).css("background-color", "");
$(idName).val("");
});
}
This is unfortunately not doing anything. When not in a loop, with hardcoded variable names, the code works, but I need this to be dynamic and constructed through a loop. The 11 count that is shown will eventually be a dynamic variable so I can't do hard coding....
Thanks for the help!
UPDATE: As requested, here is the not in the loop code:
$(".feedbackq0").toggle(
function () {
$(".feedbackq0").each(function () {
$(".feedbackq0").css("background-color", "");
});
$(this).css("background-color", "red");
var value = $(this).val();
$("#feedbackq0").val(value);
},
function () {
$(this).css("background-color", "");
$("#feedbackq0").val("");
});
$(".feedbackq1").toggle(
function () {
$(".feedbackq1").each(function () {
$(".feedbackq1").css("background-color", "");
});
$(this).css("background-color", "red");
var value = $(this).val();
$("#feedbackq1").val(value);
},
function () {
$(this).css("background-color", "");
$("#feedbackq1").val("");
});
$(".feedbackq2").toggle(
function () {
$(".feedbackq2").each(function () {
$(".feedbackq2").css("background-color", "");
});
$(this).css("background-color", "red");
var value = $(this).val();
$("#feedbackq2").val(value);
},
function () {
$(this).css("background-color", "");
$("#feedbackq2").val("");
});
One way to do this (without seeing your HTML for further simplifications) is to put the index number on the object before your event handlers using .data() so it can be retrieved later upon demand independent of the for loop index which will have run its course by then:
var className, idName, i;
for (i = 0; i < 11; i++) {
className = ".feedbackq" + i;
$(className).data("idval", i).toggle(
function () {
var idVal = $(this).data("idval");
$(".feedbckq" + idVal).css("background-color", "");
$(this).css("background-color", "red");
var value = $(this).val();
$("#feedbackq" + idVal).val(value);
},
function () {
var idVal = $(this).data("idval");
$(this).css("background-color", "");
$("#feedbackq" + idVal).val("");
});
}
Note: I've made a bunch of other simplifications too:
I declare multiple variables with one var statement.
toString(i) is not needed to add a number onto the end of a string and you had it mispelled too (with the wrong capitalization)
.each() is not needed to apply .css() to every item in a jQuery collection
I suspect that if we could see your HTML, we could significantly simplify this further as there are probably relationships between items that could be exploited to reduce code, but without the HTML we can't offer any advice on that.
You probably fell victim to the closures-inside-for-loops bug. You need the code inside the loop to be in a separate function, so each iteration gets its own className variables instead of them sharing the variables.
You could do this by crating a named function or by using a jQuery iterator function with a callback instead of a for loop
var toggle_stuff = function(i){
var className = ".feedbackq" + i; //The variables are local to just this iteration now
var idName = "#feedbackq" + i; //No need to call toString explicitly.
//And so on...
}
for(var i=0; i<11; i++){
toggle_stuff(i)
}
I kinda suspect that you are calling the wrong function, should be .toString() instead of .ToString().
Note that JavaScript is case-sensitive.
But if I write the code anyway I will ignore the .toString() part and use the numeric value directly...

Update happens only on the last row, instead of first

function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
var dataDump = {};
for (var key in obj[i]) {
var textField = Ti.UI.createTextField(pm.combine($$.labelBrown, {
left: 200,
height:35,
value:obj[i][key],
width:550,
keyboardType:Ti.UI.KEYBOARD_NUMBER_PAD,
layout:'horizontal',
backgroundColor:'transparent',
id:i
}));
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
globalData.push(dataDump);
}
}
I am using the simlar code for Adding the data and it works fine. I posted the problem yesterday and it got resolved...
Last Object is always getting updated?
Now when i go to edit page, it shows me four text fields or number of text fields added... now when i edit something and click on save... the value get's updated on the fourth or the last TextFields Object...
Don't define functions inside loops. Computationally expensive and leads to problems, like this one. Here's a fix that should solve it:
function createTextFields(obj) {
var callback = function (vbKey, localDump) {
return function (e) {
localDump[vbKey] = e.source.value;
};
}
var i;
var max = obj.length;
for (i = 0; i < max; i++) {
var dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callBackInstance = function(keyn, dataDump);
}
globalData.push(dataDump);
}
}
JavaScript does not have block level scope, so your variables dataDump and callback, though "declared" inside for-loops actually belong to the function. As in, you're saving a value to dataDump, then you're overwriting it, each time you go through the loop. Which is why finally only the code that operated on the last value remains.
Take a look at What is the scope of variables in JavaScript? too.

Categories

Resources