Javascript can't have multiple setTimeout functions in loop - javascript

I have this segment of code that allows you set CSS changes on a timed delay. It works perfect except that it only allows you to have one instance, while I need for it to allow many. It currently just takes the last element from the loop and keeps the timeout function. Is there a way to let ALL the timeout functions from this loop to be saved and ran? I'm thinking that it is just that the setTimeout function is being overridden each time rather than being a unique function.
Note: I am getting NO console errors
Javascript (inside an onload function)
elems = _('[data-timecss]'); //function to return elems via querySelectorAll()
for (var i=0; i<elems.length; i++) {
var tempelem = elems[i];
var c_info = elems[i].dataset.timecss.split(","); //split to get time
setTimeout(function() {
var css_e = c_info[1].split(";"); //split to get css properties
for (var c=0; c<css_e.length; c++) {
var css_elem = css_e[c].split(":"); //split property and value
tempelem.style.setProperty(css_elem[0], css_elem[1]); //set value
}
}, c_info[0]); //set time
}
The HTML
<div class="block">
<p data-timecss="2000,color:green;font-weight:bold;">Change to green after 2000ms</p>
<p data-timecss="5000,display:none;">Hide this block after 5000ms</p>
</div>
Whichever data-timecss is last, will run correctly. So I can tell that the timeout function is just being overriden each time. Does anyone have any ideas on how to make these unique, but also keep it as dynamic as I have it?

you need closure - How do JavaScript closures work?
elems = _('[data-timecss]'); //function to return elems via querySelectorAll()
for (var i=0; i<elems.length; i++) {
(function(i) { // added this
var tempelem = elems[i];
var c_info = elems[i].dataset.timecss.split(","); //split to get time
setTimeout(function() {
var css_e = c_info[1].split(";"); //split to get css properties
for (var c=0; c<css_e.length; c++) {
var css_elem = css_e[c].split(":"); //split property and value
tempelem.style.setProperty(css_elem[0], css_elem[1]); //set value
}
}, c_info[0]); //set time
}(i)); // added this
}

Related

Passing variables through addEventListener

I am trying to add event to all inputs, get the value (number) from those and insertHtml them into span elements.
This is my javascript code. I have no idea how to pass the variables.
var input_selector = document.querySelectorAll('.coins_n'); // input number elements
var price_selector = document.querySelectorAll('.price'); // span elements
for(var i = 0; i <= input_selector.length; i++) {
var input = input_selector[i];
var price = price_selector[i];
input.addEventListener('input', function(){
console.log(price); // not working
console.log(input); // not working
price.innerHTML = input.value; // not working
})
}
The problem here has to do with scoping of your variables. var is a weird one, whose scope isn't really limited to the block, but to the containing function. The following two are (essentially) the same:
var input;
var i = 0;
for(; i < input_selector.length; i++) input = input_selector[i];
and
for(var i = 0; i < input_selector.length; i++) var input = input_selector[i];
both create a variable named input in global scope, and then update that variable. That means that any functions that wants to read input later will just read the last version of input, and not the version at the time you defined the handler you would trigger later.
let, however, is block scoped, and your for loop is a block. So defining let input inside the for loop will mean that input is defined uniquely for every iteration of the loop, since every time the block gets executed a new scope is created for everything in there.
The same is true for var i = 0 in your for loop - any handler that calls it later will just log the last global value of i, but if you use let, that's not the case and every iteration of the loop has its own i. So your code could simply be reduced to this:
const input_selector = document.querySelectorAll('.coins_n');
const price_selector = document.querySelectorAll('.price');
for( let i = 0; i < input_selector.length; i++ ){
input_selector[i].addEventListener('input', event => {
price_selector[i].innerHTML = event.target.value;
});
}
This is pretty complex to explain once you start typing it out, so better read what other have already written at something like MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Ok. I've managed it by using let.
var input_selector = document.querySelectorAll('.coins_n');
var price_selector = document.querySelectorAll('.price');
for(var i = 0; i < input_selector.length; i++) {
let input = input_selector[i];
let price = price_selector[i];
input_selector[i].addEventListener('input', function(){
price.innerHTML = this.value;
})
}

Javascript For loop appending child only appends first element, then throws error

I'm looping through a js object with a nested for loop, stated below, it appends the first element correctly, but then throws the following error:
Can't set the property className of an undefined reference or empty reference. (not sure if exact error, translating from Dutch...)
function allVideos() {
var sql = "SELECT videos.VideoName, videos.VideoPath FROM videos";
var resultSet = db.query(sql, {json:true}); //returns: [{"VideoName":"timelapse aethon2","VideoPath":"videos\\Roermond Papier\\160424 Time laps Aethon2.avi"},{"VideoName":"timelapse aethon3","VideoPath":"videos\\Roermond Papier\\160424 Time laps Aethon2.avi"}]
var parsed = JSON.parse(resultSet);
var parsedlength = arrLenght(parsed);
//alert(resultSet);
for(var i = 0; i < parsedlength; i++) {
var obj = parsed[i];
//alert(i);
var videoElement = document.getElementById("allVideos");
for (var key in obj) {
if(obj.hasOwnProperty(key)) {
videoElement.appendChild(document.createElement('div'));
videoElement.children[i].id='allVid' + i;
videoElement.children[i].className='col-md-4 col-xs-12';
//alert(typeof key)
var card = document.getElementById('allVid' + i);
alert(i);
card.appendChild(document.createElement('div'));
card.children[i].className='card card-block';
card.children[i].innerHTML = "<h3 class='card-title'>" + obj['VideoName'] + "</h3><button class='btn btn-primary'>Selecteren</button>"
}
}
}
}
[EDIT] added screenshot of how it looks
Your code has some significant logic issues. You're using nested loops, but appending to an element assuming that the outer loop counter will let you index into that element's children to get the element you just appended. Later, you try to get that same element again using getElementById. Then, you append a new element to your newly-created element, but try to access that new element using children[i] on the one you just created — at that point, the card element will only have a single child, so as of the second outer loop, it will fail.
createElement returns the element to you, so there's no reason at all to try to access it via children[i] (either time) or getElementById.
See comments:
function allVideos() {
var sql = "SELECT videos.VideoName, videos.VideoPath FROM videos";
var resultSet = db.query(sql, {json:true});
var parsed = JSON.parse(resultSet);
var parsedlength = arrLenght(parsed);
for(var i = 0; i < parsedlength; i++) {
var obj = parsed[i];
//alert(i);
var videoElement = document.getElementById("allVideos");
for (var key in obj) {
if(obj.hasOwnProperty(key)) {
// Create the card, give it its id and class
var card = document.createElement('div');
card.id='allVid' + i;
card.className='col-md-4 col-xs-12';
// Create the div to put in the card, give it its class and content
var div = document.createElement('div');
card.appendChild(div);
div.className='card card-block';
div.innerHTML = "<h3 class='card-title'>" + obj['VideoName'] + "</h3><button class='btn btn-primary'>Selecteren</button>"
// Append the card
videoElement.appendChild(card);
}
}
}
}
Side note: arrLenght looks like a typo (it should be th, not ht), but moreover, there's no reason to use a function to get the length of an array; it's available via the array's length property: parsedLength = parsed.length.
Side note 2: You may find these ways of looping through arrays useful.
Your problem is the if within the nested for:
if(obj.hasOwnProperty(key)) { ...
The variable i is increased even if the property is not "owned" (when the if condition returns false), so next time that the condition is true, i is out of bounds.

Phantomjs change object property inside page evaluate

I'm new to JavaScript and PhantomJS. I'm trying to store links from a webpage and then modify. The problem is that I can't modify my array after page.evaluate.
var player = 0;
var links = page.evaluate(function() {
var a = document.querySelectorAll(".link[rel='nofollow']:not(#bg)");
for(var i =0; i < a.length; i++) {
array.push({
"html":a[i].innerHTML,
"link":a[i].href
}
);
}
return array;
});
//Not setting 1 as a value
links[player]["link"] = 1;
I think that It's happening because I have already set player value when the function is executed, but is there way that I could modify this value?
Your current code replaces a copy of a copy of a reference to the actual data that you want to change. Of course, that cannot work.
If you want to change a property/content on a page, then you need to access the page and change it:
var player = 0;
page.evaluate(function(player) {
var a = document.querySelectorAll(".link[rel='nofollow']:not(#bg)");
a[player].href = "1";
}, player);
I don't think that "1" is an appropriate value for the href value, but it looks like that is what you want to do.

Javascript: Traversing through array and accessing its various elements?

Essentially what I'm trying to do right now is, given some input text, I split it up by white space and display on a
div id= "animation"
Every time a button is clicked, the array should go forward one word.
This is my current attempt.
function displayText() {
var displayText = document.getElementbyID("animation");
var list = (document.getElementbyID("input").split(/[ \tn]+/);
for (var i = 0; i < list.length; i++) {
displayText.innerHTML = list.get[i];
}
}
Is my thought process somewhat correct? For whatever reason, it doesn't seem to be working.
there are multiple issues in your method
function displayText() {
var displayTextAnimation = document.getElementbyID("animation"); //keep variable name and method name different
var list = (document.getElementbyID("input").value).split(/[ \tn]+/); //use value property and observe extra bracket
for (var i = 0; i < list.length; i++) {
displayTextAnimation.innerHTML = list.charAt(i); //observe replacing get by charAt
}
}

addEventListener pass caller as argument

I have the following code. My problem is that when clicking, the event is firing but the argument passed is not the correct one. It is passing the last dynamically created row i.e. dataStructure.length value. Anyone knows a solution how to get around this?
var table = document.getElementById('output');
for(i =0; i<dataStructure.length; i++){
var row = document.createElement('tr');
row.setAttribute('id', i);
var url = dataStructure[i].url;
if(document.addEventListener)
row.addEventListener('click', function(){handleRowClick(i);}, false);
var obj = dataStructure[i];
var cellCount = 0;
for(field in obj){
var cell = document.createElement('td');
cell.setAttribute('id', cellCount++);
//cell.addEventListener('click', function(){window.open(dataStructureObj.links[i].url);}, false);
cell.innerHTML = obj[field];
row.appendChild(cell);
}
cellCount = 0;
table.appendChild(row);
}
}
function handleRowClick(rowClicked){
var rowHTML = rowClicked.innerHTML;
var cells = rowHTML.getElementsByTagName('td');
for(cell in cells)
{
alert(cell.value);
}
window.open(cells[1].innerHTML);
}
i in the function(){handleRowClick(i);} is the loop variable i. When the function gets called, the loop is long-finished and i holds the last value it did at the end of the loop, not the value it held when you created the function(){}.
This is the closure loop problem. See this question for solutions using either closures or Function#bind:
row.addEventListener('click', handleRowClick.bind(window, i), false);
However, you're not really using that i at the moment. It would be easier to say:
row.addEventListener('click', handleRowClick, false);
and then use this to retrieve the row:
function handleRowClick(){
window.open(this.cells[1].innerHTML);
}
(Side note: unfortunately if you add attachEvent backup in order to support IE<=8, it doesn't get this right, so you'd still be looking at closures/bind to get the object you want.)
also:
for(i =0; i<dataStructure.length; i++){
Missing var: i is an accidental global.
row.setAttribute('id', i);
Avoid getAttribute/setAttribute in HTML documents. They don't do what they should in IE. Use the DOM Level 2 HTML properties instead, they're more reliable and more readable: row.id= i.

Categories

Resources