Issues with document.appendChild() in JS - javascript

I'm developing a web page of the game Mastermind, using images instead of colors.
I generate the password using Math.floor(Math.random() * 6) + 1; to generate numbers from 1 to 6, and using that function to convert a number to an image:
var sam = document.createElement("img");
sam.src = "./samwell_tarly.jpeg";
var arya = document.createElement("img");
arya.src = "./arya_stark.jpeg";
var dany = document.createElement("img");
dany.src = "./dany.jpeg";
var jon = document.createElement("img");
jon.src = "./jon.jpeg";
var ned = document.createElement("img");
ned.src = "./ned_stark.jpeg";
var tyrion = document.createElement("img");
tyrion.src = "./tyrion.jpeg";
var ocu1 = document.getElementById("oc1");
var ocu2 = document.getElementById("oc2");
var ocu3 = document.getElementById("oc3");
var ocu4 = document.getElementById("oc4");
function intToGot(x) {
if(x==1){return arya;}
if(x==2){return sam;}
if(x==3){return ned;}
if(x==4){return dany;}
if(x==5){return jon;}
if(x==6){return tyrion;}
}
and then:
const oc1=Math.floor(Math.random() * 6) + 1;
ocu1.appendChild(intToGot(oc1));
const oc2=Math.floor(Math.random() * 6) + 1;
ocu2.appendChild(intToGot(oc2));
const oc3=Math.floor(Math.random() * 6) + 1;
ocu3.appendChild(intToGot(oc3));
const oc4=Math.floor(Math.random() * 6) + 1;
ocu4.appendChild(intToGot(oc4));
Those are my divs:
<div class="maiores" id="oc1"></div>
<div class="maiores" id="oc2"></div>
<div class="maiores" id="oc3"></div>
<div class="maiores" id="oc4"></div>
The problem i am facing is that:
When the numbers generated are all different, all of the 4 random images appear correctly, with no problems at all. But, when there is repetition, for example, if the password should be [sam,sam,dany,jon], only the last one of the repeated images appear, and the others just don't appear. In that case, the first 'sam' wouldn't appear. I can't understand how am I using wrong the appendChild function, and I need help to solve that problem.

You've only made one image node for each character. It can only go in one place.
Just generate your images on the fly, instead of ahead of time.
function makeGot(fileName) {
const n = document.createElement("img");
n.src = filename;
return n;
}
function intToGot(x) {
if(x==1){return makeGot("arya_stark.jpeg");}
if(x==2){return makeGot("samwell_tarly.jpeg");}
if(x==3){return makeGot("ned_stark.jpeg");}
if(x==4){return makeGot("dany.jpeg");}
if(x==5){return makeGot("jon.jpeg");}
if(x==6){return makeGot("tyrion.jpeg");}
}

solution
Explanation
This is because if a node is already a part of a document, then you cannot use that node reference again in another part of a document. If you do then the previous nodes get removed and inserted into new location.
const parent = document.querySelector("#parent");
const parent2 = document.querySelector("#parent2");
function createListChildElement(text) {
const element = document.createElement("li");
element.textContent = text;
return element;
}
const firstListChild = createListChildElement("First");
parent.appendChild(firstListChild);
const secondListChild = createListChildElement("Second");
parent.appendChild(secondListChild);
parent2.appendChild(firstListChild);
<h1>Second List</h1>
<ul id="parent"></ul>
<h1>Second List</h1>
<ul id="parent2"></ul>
Solution
You can create a new node/clone and append it to a new parent. You can do this using cloneNode method node
const parent = document.querySelector('#parent');
const parent2 = document.querySelector('#parent2');
function createListChildElement( text ) {
const element = document.createElement('li');
element.textContent = text;
return element;
}
const firstListChild = createListChildElement("First");
parent.appendChild( firstListChild );
const secondListChild = createListChildElement("Second");
parent.appendChild( secondListChild );
const clonedChild = firstListChild.cloneNode(true);
parent2.appendChild( clonedChild );
<h1>First list</h1>
<ul id="parent"></ul>
<h1>Second list</h1>
<ul id="parent2"></ul>

Related

$ dot each not working for recursion (JS)

I have a loop in which I am calling rec_append() recursively, apparently the first pass alone works, then the loop stops.
I have an array of 4 elements going into that $.each loop but I see only the first element going into the function recursively. Help!
I switched it for a element.forEach but that gives me only the second element and I am stuck, is there a better solution to process a tree of elements? My array is a part of a tree.
var data = JSON.parse(JSON.stringify(result))
var graph = $(".entry-point");
function rec_append(requestData, parentDiv) {
var temp_parent_details;
$.each(requestData, function (index, jsonElement) {
if (typeof jsonElement === 'string') {
//Element construction
//Name and other details in the form of a : delimited string
var splitString = jsonElement.split(':');
var details = document.createElement("details");
var summary = document.createElement("summary");
summary.innerText = splitString[0];
details.append(summary);
temp_parent_details = details;
parentDiv.append(details);
var kbd = document.createElement("kbd");
kbd.innerText = splitString[1];
summary.append(' ');
summary.append(kbd);
var div = document.createElement("div");
div.className = "col";
details.append(div);
var dl = document.createElement("dl");
div.append(dl);
var dt = document.createElement("dt");
dt.className = "col-sm-1";
dt.innerText = "Path";
div.append(dt);
var dd = document.createElement("dd");
dd.className = "col-sm-11";
dd.innerText = splitString[2];
div.append(dd);
var dt2 = document.createElement("dt");
dt2.className = "col-sm-1";
dt2.innerText = "Type";
div.append(dt2);
var dd2 = document.createElement("dd");
dd2.className = "col-sm-11";
dd2.innerText = splitString[1];
div.append(dd2);
} else {
$.each(jsonElement, function (jsonElementArrIndx, jsonChildElement) {
rec_append(jsonChildElement, temp_parent_details); //Only 1 pass works, rest skip
});
}
});
}
rec_append(data, graph);
Sample data:enter image description here

Javascript - can't iterate over object in incremental search

I'm very new to javascript/dev so I hope there is a an obvious solution that I've not thought of. My code returns search items from TVMaze.com API. The feature giving me trouble is the incremental search (as a user types in input box, the code returns and displays images by creating a new div and appending images, removing and replacing the an div).
My problem is that on deleting all characters from input box, I receive the error: "Uncaught (in promise) TypeError: shows is not iterable" which I suppose means that there is no object to iterate over? Thanks in advance for any help.
const input = document.querySelector("#query");
input.addEventListener("input", async function (e) {
e.preventDefault();
const searchTerm = e.target.value;
const config = { params: { q: searchTerm } };
const res = await axios.get(`http://api.tvmaze.com/search/shows?`, config);
makeImages(res.data);
clearList();
});
const makeImages = (shows) => {
const div = document.createElement("div");
for (let result of shows) {
if (result.show.image) {
const img = document.createElement("IMG");
img.className += "resultImage";
img.src = result.show.image.medium;
const title = document.createElement("h3");
title.className += "resultTitle";
title.innerText = result.show.name;
const year = document.createElement("h4");
year.className += "score";
year.innerText = result.show.premiered;
var sub = year.innerText.substring(0, 4);
var yearNum = parseInt(sub);
div.append(year);
div.append(img);
div.append(title);
document.body.appendChild(div);
}
if (yearNum <= 2000) {
var retro = document.createElement("h5");
retro.className = "retro";
retro.innerText = "retro";
div.append(retro);
}
}
};
let clearList = () => {
var allImg = document.querySelectorAll("IMG");
if (allImg.length === 0) {
document.createElement("div");
return makeImages();
}
var oldDiv = document.querySelector("div");
oldDiv.remove();
console.log(oldDiv);
};

Inserting probabilities in a card game

I'm looking to create a code in which a card would be drawn randomly by pressing a button. However, I would like some cards to be rarer than others, but I don't know how to integrate this, the code is not at all my favorite domain ...
Thank you very much for your help and the tips you can give me!
var spongebob = "<img src = 'http://www.homastudio.com/img/spongebob.png'>";
var patrick = "<img src = 'http://www.homastudio.com/img/patrick.png'>";
var squidward = "<img src = 'http://www.homastudio.com/img/squidward.png'>";
var sandy = "<img src = 'http://www.homastudio.com/img/sandy.png'>";
var krabs = "<img src = 'http://www.homastudio.com/img/krabs.png'>";
var larry = "<img src = 'http://www.homastudio.com/img/larry.png'>";
var images = [spongebob, patrick, squidward, sandy, krabs, larry]
var names = ["Spongebob Squarepants", "Patrick Star", "Squidward Tentacles", "Sandy Cheeks", "Eugene Krabs", "Larry Lobster"]
function displayImage(){
var rn = Math.random();
rn = rn*6;
rn = Math.floor(rn);
document.getElementById("pic").innerHTML=images[rn];
document.getElementById("name").innerHTML=names[rn];
}
What you're after is called a Weighted Random selection, that is to say it is still a random selection, but some items are weighted higher. The algorithm is fairly simple.
You start with a list of items, and you assign each one a relative weight. The higher the weight, the more likely it is to be picked. Then, pick a random number between 0 and the total weight of all items and you step through the items subtracting each items weight from the random number you selected. when the number reaches zero, you have your item.
Heres an example.
function WeightedBroker(){
this.totalWeight = 0;
this.items = [];
this.addItem = function(value,weight){
this.items.push({value,weight});
this.totalWeight+= weight;
}
this.pickItem = function(){
var rnd = Math.floor(Math.random() * this.totalWeight);
for(var i=0;i<this.items.length;i++){
if(rnd<=this.items[i].weight)
return this.items[i].value;
rnd -= this.items[i].weight;
}
}
}
var broker = new WeightedBroker();
broker.addItem("a",100);
broker.addItem("b",10);
broker.addItem("c",1);
for(var i=0;i<10;i++)
console.log(broker.pickItem());
Applying this to your code is pretty easy, you will want to change those 2 arrays (one with img html, and one with names) to objects containing the properties name and src. You add each of these items to the broker I wrote above with appropriate weights for each according to your requirement. Something like this:
function WeightedBroker(){
this.totalWeight = 0;
this.items = [];
this.addItem = function(value,weight){
this.items.push({value,weight});
this.totalWeight+= weight;
}
this.pickItem = function(){
var rnd = Math.floor(Math.random() * this.totalWeight);
for(var i=0;i<this.items.length;i++){
if(rnd<=this.items[i].weight)
return this.items[i].value;
rnd -= this.items[i].weight;
}
}
}
var broker = new WeightedBroker();
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/spongebob.png'>",name:"Spongebob Squarepants"},100);
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/patrick.png'>",name:"Patrick Star"},100);
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/squidward.png'>",name:"Squidward Tentacles"},100);
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/sandy.png'>",name:"Sandy Cheeks"},100);
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/krabs.png'>",name:"Eugene Krabs"},100);
broker.addItem({src:"<img src = 'http://www.homastudio.com/img/larry.png'>",name:"Larry Lobster"},100);
var item = broker.pickItem();
document.getElementById("pic").innerHTML=item.src;
document.getElementById("name").innerHTML=item.name;
<div id="name"></div>
<div id="pic"></div>
instead of having the array just have images do something like the following (where a low number in 'rarity' means more rare:
var spongebob = {
url:"<img src = 'http://www.homastudio.com/img/spongebob.png'>",
rarity: 1
};
var patrick = {
url:"<img src = 'http://www.homastudio.com/img/patrick.png'>",
rarity: 2
};
var squidward = {
url:"<img src = 'http://www.homastudio.com/img/squidward.png'>",
rarity: 4
};
var images = [spongebob, patrick, squidward];
var imageChoiceArr = [];
for(var i=0;i<images.length;i++) {
image = images[i];
for(var ii=0;ii<image.rarity;ii++) {
imageChoiceArr.push(image.url);
}
}
Then pick randomly from the 'imageChoiceArr'

Pulling a non-repeating, random image from an array

I have searched the forum thoroughly for a solution to this with no luck. Hoping someone can help me here.
I have an empty div with no background image. When a user clicks on it, I have a function that sets the background image like this:
function myAnimation(element) {
var images = ["url(images/pig.png)", "url(images/cat.png)", "url(images/frog.png)", "url(images/dog.png)"];
var random = Math.floor(Math.random() * 4);
element.style.backgroundImage = images[random];
}
Although this works, the problem I'm having is finding the solution to when the user clicks on the image again. As there is only four possible numbers generated, I keep getting a repeating number from the var random method. I think I need a way to check the last number generated and if it's the same, it should try and generate a different number. This way there will be no repeating images. Hope this makes sense. Just so you know, I'm new to programming and only learning Javascript for the last 3 months. I think I may need a loop or another function inside this function, not sure.
I am checking prevNo with generated random number. If both are same, then call the function again. Else assign the random number to prevNo and load the image.
var prevNo = -1;
function myAnimation(element) {
var images = ["url(images/pig.png)", "url(images/cat.png)", "url(images/frog.png)", "url(images/dog.png)"];
var random = Math.floor(Math.random() * 4);
if( random == prevNo) {
myAnimation(element);
return;
} else {
prevNo = random;
}
element.style.backgroundImage = images[random];
}
You can filter your images, considering all items except the current background element, and take a random element of the result.
This is a example of how you can accomplish this:
function myAnimation(element) {
var currentBackgroundImage = element.style.backgroundImage;
var images = [
"url(https://place-hold.it/100x100&text=1)",
"url(https://place-hold.it/100x100&text=2)",
"url(https://place-hold.it/100x100&text=3)",
"url(https://place-hold.it/100x100&text=4)",
];
var restOfImages = images.filter(function (image) {
return image !== currentBackgroundImage;
})
var random = Math.floor(Math.random() * restOfImages.length);
element.style.backgroundImage = restOfImages[random];
}
Another way, without filter:
function myAnimation(element) {
var currentBackgroundImage = element.style.backgroundImage;
var images = [
"url(https://place-hold.it/100x100&text=1)",
"url(https://place-hold.it/100x100&text=2)",
"url(https://place-hold.it/100x100&text=3)",
"url(https://place-hold.it/100x100&text=4)",
];
var restOfImages = [];
for(var i = 0; i < images.length; i++) {
if (images[i] !== currentBackgroundImage) {
restOfImages.push(images[i]);
}
}
var random = Math.floor(Math.random() * restOfImages.length);
element.style.backgroundImage = restOfImages[random];
}
And here is a working snipplet:
function myAnimation(element) {
var currentBackgroundImage = element.style.backgroundImage;
var images = [
"url(https://place-hold.it/100x100&text=1)",
"url(https://place-hold.it/100x100&text=2)",
"url(https://place-hold.it/100x100&text=3)",
"url(https://place-hold.it/100x100&text=4)",
];
var restOfImages = images.filter(function (image) {
return image !== currentBackgroundImage;
})
var random = Math.floor(Math.random() * restOfImages.length);
element.style.backgroundImage = restOfImages[random];
}
var el = document.getElementById('container');
el.onclick= function() { myAnimation(el); };
#container{
width: 100px;
height: 100px;
background-color: red;
}
<div id="container"></div>
You can repeatedly use Array.splice to remove a random element.
var images = [...
var random = Math.floor(Math.random() * 4);
element.style.backgroundImage = images.splice(random, 1)[0];
I'm using the same function across the entire site so I'm using local storage with a window onload call to headerPic():
const originalArr = ["image1.jpg", "image2.jpg", etc...];
function headerPic() {
let headerImgArray = localStorage.getItem("headerList") ? JSON.parse(localStorage.getItem("headerList")) : originalArr;
if (headerImgArray.length < 1) {
headerImgArray = originalArr;
}
const randomName = headerImgArray[Math.floor(Math.random() * headerImgArray.length)];
document.getElementById("headerImg").src = `imagesHeader/${randomName}`;
headerImgArray.splice(headerImgArray.indexOf(randomName), 1);
if (headerImgArray.length < 1) {
headerImgArray = originalArr;
}
localStorage.setItem("headerList", JSON.stringify(headerImgArray));
}

InnerHTML dissapearing on setInterval Function

I have written a simple script and it's job is to change a innerHTML of a random element in one section of the page. Somehow when I call the function, and set it to fire every 1 second, when innerHTML of a specific element is changed, it doesn't stay that way , it just clears itself and moves on to another element. Can anyone help me with this. Here is the code, thanks in advance.
window.onload = function() {
var box1 = document.getElementById("one");
var box2 = document.getElementById("two");
var box3 = document.getElementById("three");
var box4 = document.getElementById("four");
var box5 = document.getElementById("five");
var box6 = document.getElementById("six");
var box7 = document.getElementById("seven");
var box8 = document.getElementById("eight");
var headingArray = ["RAVE", "RUN", "PAINT"];
var iconArray = ["ion-usb", "ion-android-walk", "ion-android-color-palette"];
var paragraphArray = ["Wanna good time? <br> Check out nearest party centres","Check out running tracks", "Ckeck out painting places"];
var boxArray = [box1,box2,box3,box4,box5,box6,box7,box8];
var heading = document.createElement("h2");
var icon = document.createElement("i");
var paragraph = document.createElement("p");
function getRandomNumberForContent() {
var randomHeading = Math.round(Math.random()*2) + 0;
return randomHeading;
}
function getRandomNumberForBox() {
var randomNumber = Math.round(Math.random()*7) + 0;
return randomNumber;
}
function changeBox() {
var random = getRandomNumberForContent();
heading.innerHTML = headingArray[random];
icon.className = "icon "+iconArray[random]+" big";
paragraph.innerHTML = paragraphArray[random];
var randomNum = getRandomNumberForBox();
boxArray[randomNum].innerHTML = "";
boxArray[randomNum].appendChild(heading);
boxArray[randomNum].appendChild(icon);
boxArray[randomNum].appendChild(paragraph);
}
setInterval(changeBox,1000);
}
You are somewhat moving the element to the new div each time the function is called, because you are assigning as a child the same element, not a copy of it.
You should create the new element inside the changeBox function.
That's the answer. If you create it outside the function, they will be a unique element that you are assigning either to one div or another.
I assume that it moves to another element because you append the same elements somewhere else. You could clone the elements when you append them:
boxArray[randomNum].appendChild(heading.cloneNode(true));
boxArray[randomNum].appendChild(icon.cloneNode(true));
boxArray[randomNum].appendChild(paragraph.cloneNode(true));

Categories