Elements not added to DOM by generator function - javascript

I have been pulling my hair out trying to make this simple code work. It should render input fields in the given DOM, but it doesn't. Why not?
var elems = 10;
function generateElems() {
for (var i = 0; i < elems; i++) {
document.getElementsByTagName("div")[0].appendChild(document.createElement('input'));
}
//Clean up
var obj = null;
var elems = null;
}
generateElems();

Working DEMO
You are dealing with JavaScript variable hoisting here. Remove this line var elems = null; and your code should work.
It is considered best practise in JavaScript to declare all variables at the top of the function body.
Read this article for more information on JavaScript hoisting.
As we are discussing best practises, it's worth making a note that appending elements in loops is a bad idea for performance. You should use createDocumentFragment instead to append the elements to and then dump this to DOM. It saves expensive document reflows and makes significant difference in performance.
var elems = 10;
function generateElems() {
var d=document.createDocumentFragment();
for (var i = 0; i < elems; i++) {
d.appendChild(document.createElement('input'));
}
document.getElementsByTagName('div')[0].appendChild(d);
//Clean up
//var obj = null;
//var elems = null; ----> Commented out this line, it was causing the problem.
}
generateElems();

Don't set elms to null.
var elems = 10;
function generateElems() {
for (var i = 0; i < elems; i++) {
document.getElementsByTagName("div")[i].appendChild(document.createElement('input'));
}
}
generateElems();

Related

How to fix scope issues when using an onclick event in a for loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I am curretly experiencing difficulties implementing an onclick event within a for loop. Instead of alerting the respective value it always returns undefined (presumably a scope problem, because the iteration itself works fine)
Until now I tried to pass on the i variable to the onclick function; however, with little success
for (var i = 0; i < timeSpanLength; i++) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[i];
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
}
The expected outcome is to trigger an alert which displays the same month which is displayed in the span element above. I need the variable to pass it on to another function.
Any help is highly appreciated since I am beginner in javascript.
for (var i = 0; i < timeSpanLength; i++) {
(function (index) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[index];
//onclick event
reference_month.onclick = function() {
var month_beginning = signup_date;
var month_end = time_span[index];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
})(i);
}
This callback function handler is forming a closure with respect to the outer scope. Also var has a function scope, so in essence the block of code can be re-written as:
var i;
for (i = 0; i < timeSpanLength; i++) {
...
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
...
}
So the var i is hoisted to the top and when the loop completes the value of i is timeSpanLength.length and this is what you use to access time_span[i] and that returns undefined.
Since with var the binding remains the same, the handlers registered will be referring the last value of i in the loop.
So you either need to use let in the for-loop:
for (let i = 0; i < timeSpanLength; i++) { ... }
Or an IIFE which forms a new scope bound to each new value of i from the loop:
for (var i = 0; i < timeSpanLength; i++) {
(function(i){
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
})(i)
}

Why can't I remove my event listener?

I have an issue with removeEventListener, it doesn't seem to work at all, I've seen some other questions on this site but I don't get it, can you help me?
displayImg() {
console.log('img')
for (var i = 1; i <= 4; i++) {
var line = "l"+i;
var position = 0;
var addDivLine = document.createElement('div');
addDivLine.className = 'line';
addDivLine.id = line;
document.getElementById('container').appendChild(addDivLine);
for (var j = 1; j <= 7; j++) {
var block = "b"+j;
var element = line+"-"+block;
var addDivBlock = document.createElement('div');
addDivBlock.className = 'block';
addDivBlock.id = element;
document.getElementById(line).appendChild(addDivBlock);
memory.addEvent(element);
};
};
showImage(event) {
event.preventDefault();
memory.clickedBlock++;
var block = event.target.id;
memory.removeEvent(block);
}
addEvent(id){
document.getElementById(id).addEventListener('click', function(){memory.showImage(event)});
},
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', function(){memory.showImage(event)});
},
I am creating div elements then put an eventListener on them, I call the same function to remove the event, I use the same id, is there something that I forgot? I probably don't fully understand how it really works.
Thanks a lot!
In this two lines:
.addEventListener('click', function() { memory.showImage(event) });
and
.removeEventListener('click', function() { memory.showImage(event) });
function() { memory.showImage(event) } are two different functions. You need to provide reference to the same function in both cases in order to bind/unbind listener. Save it so some variable and use in both places:
.addEventListener('click', memory.showImage);
.removeEventListener('click', memory.showImage);
For example using directly memory.showImage will work properly as it's the same function in both cases.
The function looks like the same but its reference would be different. So, define the function in a scope where it's available for both function and use the reference in both case.
var callback = function(){memory.showImage(event)};
addEvent(id){
document.getElementById(id).addEventListener('click', callback);
}
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', callback);
}

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

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