Having trouble detecting undefined objects in an array - javascript

Quick bit about my background:
-been learning for about 3 months;
-work in tech support for a small software company. 2 years exp.
-a lot of knowledge is secondhand and I am still learning the basics
I am trying to create an object every second. The object is created directly to the last position of an array that remembers a set quantity of objects created before the most recent one
function Fruit(name, position) {
this.name = name;
this.position = position;
}
var showXMostRecentFruits = 20;
var fruitCounter = 0;
function generateName() {
var name = 'Experimental Fruit' + fruitCounter;
return name;
}
var fruitsArray = [];
function shiftFruits() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i] = fruitsArray[i + 1];
}
function updateFruitPositions() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
}
var fruitTimer; //used for setting and clearing setTimeout
function createNewFruit() {
shiftFruits();
fruitsArray[showXMostRecentFruits - 1] = new Fruit(generateName());
updateFruitPositions();
fruitCounter += 1;
fruitTimer = setTimeout(function() {
createNewFruit();
}, 1000);
}
Say the function createNewFruit() is run once
createNewFruit();
Then I try to pull some meaning from the array
console.log(fruitsArray[19];
All I get is:
Fruit {}
undefined
This issue is when I want to run a loop (see updateFruitPositions()) that updates a propery of each object in the array, an error is returned that the objects are undefined. I get that they are undefined because they are not assigned to unique variables (at least not that I'm aware of). How can I identify the objects or how can I create unique containers for them so I access them in the array?

You need to test whether a given element is set to something before attempting to write to one of its properties.
Instead of this...
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
Use this:
for (i = 0; i < showXMostRecentFruits; i++) {
if (fruitsArray[i])
fruitsArray[i].position = i;
}
You fill the array from the end, staring with element 20. Without the if (fruitsArray[i]), you're attempting to set undefined.position = i for the first 19 elements.

You could replace the showFruits function with something much more efficient:
function shiftFruits() {
if (fruitsArray.length > showXMostRecentFruits) {
fruitsArray.shift();
}
}
and updateFruitPositions only needs to update members that exist, the length is controlled by shiftFruits:
function updateFruitPositions() {
for (i = 0; i < fruitsArray.length; i++) {
fruitsArray[i].position = i;
}
}
or where forEach is supported:
function updateFruitPositions() {
fruitsArray.forEach(function(fruit, i){fruit.position = i});
}
so it only visits members that exist. And the createNewFruit has:
fruitsArray.push(new Fruit(generateName());

Related

jQuery: passing variable to function declared inside for loop [contesting duplicate]

I ask this question again as user Cerbrus have marked the previous question as a duplicate of this question.
Can someone be so kind to show me how the question indicated by this user, should solve the code below? I can't find a match between those situations (even thought they are similar).
I need to pass a variable to a function inside a for loop. Here's an example:
var mainObj = [],
subArr = ['val1', 'val2'],
tmp;
for (var i = 0; i < subArr.length; i++) {
tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
myFunc(tmp);
}
});
}
Here I have 2 problems:
why do i have to assign subArr[i] to tmp? Using myFunc(subArr[i]) will return that i is undefined?
why in myFunc i only receive the last value of subArr array?
UPDATE
I've updated the code as follows but i get TypeError: funcs[j] is not a function
var mainObj = [],
subArr = ['val1', 'val2'],
tmp,
funcs = [];
function createfunc(i) {
return function() { console.log("My value: " + i); };
}
for (var i = 0; i < subArr.length; i++) {
funcs[i] = createfunc(subArr[i]);
}
for (var j = 0; j < subArr.length; j++) {
tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
funcs[j]();
}
});
}
Simply use let :
for (var i = 0; i < subArr.length; i++) {
let tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
myFunc(tmp);
}
});
}
Or why cant you simply copy the value into the object?:
for (var i = 0; i < subArr.length; i++) {
mainObj.push({
tmp:subArr[i],
key: function(varsFromLibrary) {
myFunc(this.tmp);
}
});
}
Another try of explaining:
Lets imagine youre a byciclist. You want to measure your speed so you ask 10 friends of you to stand next to the route at certain points and to tell you your speed. Some pseudocode:
const friends = [];
var speed = 20;//youre really fast
for(var point = 1; point < 10; point++){
speed -= 2;//youre slowing down
friends.push({
ask(){
console.log(point, speed);
}
});
}
Now afterwards you stand at the last point 10 together with your friends and you ask them for the current speed and the point they stay at. What will they tell you? Exactly, they are all standing next to you at point 10 and your current speed is 0. You asked them for the current speed and not to remember the current speed. If you want them to remember it, they need to write it down:
friends.push({
speed,//every friend object has the current value stored
point,
ask(){ console.log(this.speed,this.point)}
});
Or you need to create 10 parallel universes your friends stay in, so if you ask them for your speed they will still see you driving next to them:
for(let point = 1; point < 10; point++){
let localspeed = (speed -= 2);//youre slowing down
why do i have to assign subArr[i] to tmp?
You don't. That isn't the solution proposed by the duplicate question.
Using myFunc(subArr[i]) will return that i is undefined?
i won't be undefined. It will be the same as subArr.length.
subArr[i] will be undefined, because subArr.length is the number of items in the array and the array is zero indexed.
why in myFunc i only receive the last value of subArr array?
Because that is the last value you copied to tmp before the loop ended.
As the high rated answer on the question you link to says, you need to copy i or subArr[i] to a new scope so it won't change next time you go around the loop.

Why are my variables "undefined"

I am new to Javascript and I am building my very first simple game "battle ship". Now I have a very frustrating problem.
I declared a variable in the global scope, but I cannot seem to use it in my program. If I do console.log(x); it says the variable is undefined.
When the user clicks a button called "Play" I want the background color of all cells to turn back to lightblue again;
function playGame() {
x.style.backgroundColor = 'lightblue';
}
This is the error message:
Uncaught TypeError: Cannot set property 'backgroundColor' of undefined
Even if I would replace the x with document.getElementsByTagName("td"); it does not work
var tabel = document.getElementById("slagveld");
var x = document.getElementsByTagName("td");
var gekozenVakken = [];
var hit = 0;
var pogingen = 0;
function reset() {
location.reload();
}
function veranderKleur(geklikteCel) {
if (gekozenVakken.length < 3) {
geklikteCel.style.backgroundColor = 'yellow';
gekozenVakken.push(parseInt(geklikteCel.innerHTML));
}
if (gekozenVakken.length === 3) {
alert("guess the position of the ship.");
}
}
function playGame() {
geklikteCel.style.backgroundColor = 'lightblue';
}
for (var i = 0; i < tabel.rows.length; i++) {
for (var j = 0; j < tabel.rows[i].cells.length; j++) {
tabel.rows[i].cells[j].onclick = function() {
veranderKleur(this);
}
};
}
I hope someone could help me out.
GetElementsByTagName() returns an array of objects, even if there is only 1 object with that tag. Since the array itself does not have the property .style, you get an 'undefined' message when accessing sub-members of .style.
You need to select an element from that array to access its properties:
x[0].style.backgroundColor = 'lightblue';
With the [] square braces you can access any element of an array. In my example, we're accessing the first element (at index 0). Which element you need is up for you to determine.
That function returns a list of nodes, even though there's only one
use document.getElementsByTagName("td")[0].style.color= 'lightblue';
function playGame() {
x[0].style.backgroundColor = 'lightblue';
}
fiddle
If the line
var x = document.getElementsByTagName("td");
is before your <table>, that element won't exist when the line is executed.
Ensure you are adding your script at the end of your HTML document or it will return an empty array (please note that the function is called "get elements by tag name", in plural).
I assume you're running this in your browser and this is your entire code.
Problem here: You're trying to read content from the DOM, however, your DOM might not be entirely loaded at the time you try to read from it.
This is easily fixable though:
In your HTML:
<body onload="start();"></body>
And in your JS code:
function reset() {
location.reload();
}
function veranderKleur(geklikteCel) {
if (gekozenVakken.length < 3) {
geklikteCel.style.backgroundColor = 'yellow';
gekozenVakken.push(parseInt(geklikteCel.innerHTML));
}
if (gekozenVakken.length === 3) {
alert("guess the position of the ship.");
}
}
function playGame() {
geklikteCel.style.backgroundColor = 'lightblue';
}
function start() {
var tabel = document.getElementById("slagveld");
var x = document.getElementsByTagName("td");
var gekozenVakken = [];
var hit = 0;
var pogingen = 0;
for (var i = 0; i < tabel.rows.length; i++) {
for (var j = 0; j < tabel.rows[i].cells.length; j++) {
tabel.rows[i].cells[j].onclick = function() {
veranderKleur(this);
}
};
}
}
This will ensure that all your reading/executing will perform when the entire DOM was loaded.
If you're not running code in your browser, please give more details in your question.
I would guess it's because the code for initializing the variables never actually gets called. Try doing it as follows (you need jQuery for it):
$('document').ready(function(){
var tabel = document.getElementById("slagveld");
var x = document.getElementsByTagName("td");
var gekozenVakken = [];
var hit = 0;
var pogingen = 0;
});
This way as soon as your DOM is loaded into the browser the variables will get initialized.
getElementsByTagName return a list of elements with the name "td" and you don't have the property backgroundColor for the list of elements, you must use a function like foreach.
Array.prototype.forEach.call(x, function(el, i){
el.style.backgroundColor = 'yellow';
});
And each element will in "x" will have the background color yellow.
You're looking for document.getElementById

how to get incremented value in for loop after callback function in javascript?

My Requirement:
I want to get the list of values using for loops. In for loop one iteration completed one time then the callback will send that list of values(array).
Once the first iteration completed second time loop value should be get incremented value.
For example : 5 values
after 5th iteration then loop is over. then second time loop should start with '0' but here it's starting with last incremented value. please help me to achieve this.
Below code is working fine for the first time.
Callback function:
$inventoryManagement.getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId(objectId,attributeId, function(objectAttributeBlockElement) {
//$scope.val = myOwnJ;
console.log(objectAttributeBlockElement);
});
Function:
var myOwnJ = 0;
// Getting ObjectId And AttributeId Using CellId For Normal Controls
var getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId = function(objectId,attributeId, callback) {
var objectAttributeBlockElement = [];// one array
try {
// iterate over the objectAttributes
for (var i = 0; i < pageObject.objects.length; i++) {
if (pageObject.objects[i].id == objectId) {
var name = "";
var labelName = "";
var dataTypeId = "";
for (;myOwnJ < pageObject.objects[i].objectAttribute.length;) {
name = pageObject.objects[i].objectAttribute[myOwnJ].name;// got the current label name
labelName = pageObject.objects[i].objectAttribute[myOwnJ].labelName;// got the current name
dataTypeId = pageObject.objects[i].objectAttribute[myOwnJ].dataTypeId;// got the current dataTypeId
objectAttributeBlockElement.push(name,labelName,dataTypeId);
callback(objectAttributeBlockElement, myOwnJ++);
return;
}
}
}
throw {
message: "objectId not found: " + objectId
};
} catch (e) {
console.log(e.message + " in getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId");
}
};
You could pass j as an additional function parameter, such as
var getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId = function(objectId, attributeId, j, callback) {
so it won't be a local variable. Then, instead of declaring it locally, use the following:
for (j = ((j === null) ? 0 : j); j < pageObject.objects[i].objectAttribute.length; j++) {
That way, if you call your function with j, you'll get it incremented after each call.
Another approach, which I won't recommend, would be making j a global variable by declaring it ouside your function instead of passing it as a parameter. That way you don't have to modify your function declaration at all. If you're up to that, I strongly suggest modifying the variable name cause j would be too generic for a global scope variable and it will cause trouble sooner or later: use something like myOwnJ and you'll be fine.
EDIT: Full source code (as requested by the OP):
var myOwnJ = 0;
// Getting ObjectId And AttributeId Using CellId For Normal Controls
var getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId = function(objectId,attributeId, callback) {
var objectAttributeBlockElement = [];// one array
try {
// iterate over the objectAttributes
for (var i = 0; i < pageObject.objects.length; i++) {
if (pageObject.objects[i].id == objectId) {
var name = "";
var labelName = "";
var dataTypeId = "";
if(myOwnJ < pageObject.objects[i].objectAttribute.length) {
name = pageObject.objects[i].objectAttribute[myOwnJ].name;// got the current label name
labelName = pageObject.objects[i].objectAttribute[myOwnJ].labelName;// got the current name
dataTypeId = pageObject.objects[i].objectAttribute[myOwnJ].dataTypeId;// got the current dataTypeId
objectAttributeBlockElement.push(name,labelName,dataTypeId);
callback(objectAttributeBlockElement, myOwnJ++);
return;
}
else {
myOwnJ = 0;
}
}
}
throw {
message: "objectId not found: " + objectId
};
} catch (e) {
console.log(e.message + " in getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId");
}
};
What you are looking for is a global variable for 'j'. Although this is discouraged to be used.
var j=0;
var getObjectNameAndAttributeAndDataTypeIdUsingObjectAndAttributeId =
function(objectId, attributeId, callback) {
//do your stuff
//increment j
j++;
}

How to pick a random property from an object without repeating after multiple calls?

I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.

Adding a random class to element from an array that duplicates using jQuery

I'm creating a matching game and I'm trying to add a class from an array to match against.
The code I have below creates the classes I need, then randomizes them.
My problem is in the randomizeDeck function. I'm trying to add each of the classes to the specified element twice. When I console.log the code the classes gets added to the first six elements but not the last six, which I need it to do so that I have the classes to match against in the matching game I'm creating.
var cardDeck = new Array();
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
}
}
createDeck();
var randDeck = cardDeck.sort(randOrd);
function randomizeDeck() {
card.each(function(i){
$(this).addClass(randDeck[i]);
});
}
randomizeDeck();
I think your createDeck function needs to create 12 classes instead of 6. Just push each one twice:
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
cardDeck.push("card-" + i);
}
}
Then you'll have an array of 12 classes (2 each of 6 unique classes), which will be randomized and assigned to the 12 cards.
I suggest a separate variable to keep track of the index, rather that the each index. Once you've gone through the pack once, it might be a good idea to shuffle the deck again so the order is different on the second pass. YMMV.
function sortCards(randOrd) {
randDeck = cardDeck.sort(randOrd);
}
function randomizeDeck() {
var count = 0;
cards.each(function(i) {
if (i === 6) { count = 0; sortCards(randOrd); }
$(this).addClass(randDeck[count]);
count++;
});
}
Your randomizeDeck() function can be rewritten to use the same array of class names twice:
function randomizeDeck() {
card.each(function(i){
if(i < 6)
$(this).addClass(randDeck[i])
else
$(this).addClass(randDeck[i-6]);
});
}
Note: I would rewrite the variable card as $cards so that you know it's a jQuery object and in this case a collection of them. Otherwise, its hard to tell it apart from any other javascript var.
Try something like this - it's tested now updated
SEE THIS FIDDLE
http://jsfiddle.net/8XBM2/1/
var cardDeck = new Array();
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
}
}
createDeck();
var randDeck = cardDeck.sort();
alert(randDeck);
function randomizeDeck() {
var x = 0;
$('div').each(function(i){
if ( i > 5) {
$(this).addClass(randDeck[x]);
x++;
} else {
$(this).addClass(randDeck[i]);
}
});
}
randomizeDeck();

Categories