Time for populating a UI dynamically increases linearly, with each try? - javascript

Requirement:
User will enter "Number of Containers" and "Number of Controls"
Random input elements (numeric, checkbox, etc) will be created and equally distributed among the containers.
When user clicks on "Create" again, the input elements shown in the UI will be deleted and new set of random input elements will be populated again.
Issue:
Every time I create new set of input elements, the time taken for creating increases linearly up to a point then decreases little and increases again
I use the below code to empty the div that accommodates the containers and create input elements
Emptying the overall div
node.innerHTML = ""
Creating a numeric control with label
function createNumber(display) {
let controlWrap = document.createElement("div");
let label = document.createElement("label")
let control = document.createElement("input")
control.type = "number";
label.append("Numeric Input");
label.append(control);
controlWrap.append(label);
controlWrap.style.display = display;
controlWrap.classList.add("ctrl");
return controlWrap;
}
Find the entire code below,
//Constands
const CTRL_DISPLAY_TYPE = "block"
//Selection
const numOfContainers = document.querySelector("#numOfContainers");
const numOfControls = document.querySelector("#numOfControls");
const createContainersBtn = document.querySelector("#create");
const containerWrapper = document.querySelector(".containerWrapper");
const controlHeading = document.querySelectorAll(".ctrlHeading");
//Event Listeners
createContainersBtn.addEventListener("click",createContainers);
controlHeading.forEach(element => element.addEventListener("click"),expandControl);
//Support-functions
function createControl(newControlContainer){
let newControlWrapper = document.createElement("div")
newControlWrapper.classList.add("ctrlWrapper");
let newControl = createNumber(CTRL_DISPLAY_TYPE);
newControlWrapper.appendChild(newControl);
newControlContainer.appendChild(newControlWrapper);
}
function createNumber(display){
let controlWrap = document.createElement("div");
let label = document.createElement("label")
let control = document.createElement("input")
control.type = "number";
label.append("Numeric Input");
label.append(control);
controlWrap.append(label);
controlWrap.style.display = display;
controlWrap.classList.add("ctrl");
return controlWrap;
}
function calculateControlPerContainer(numOfContainers,numOfControls,maxLimit){
let controlsPerContainer = []
let pendingControls = numOfControls%numOfContainers
let controlPerContainerNum = Math.floor(numOfControls/numOfContainers)
for (let i=0;i<numOfContainers;i++){
if (pendingControls>0){
newControlsPerContainer=controlPerContainerNum+1;
controlsPerContainer.push(newControlsPerContainer);
--pendingControls;
}
else{
controlsPerContainer.push(controlPerContainerNum);
}
}
return controlsPerContainer
}
function expandControl(event){
const control = event.currentTarget.nextElementSibling;
if (control.style.display === "none"){
control.style.display = "block";
}
else {
control.style.display = "none"
}
}
//utility-functions
function removeChild(node){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
function clearNodeData(node){
node.innerHTML = ""
}
//main-Functions
function createContainers(event){
console.time("Deleting controls");
const controlsPerContainer = calculateControlPerContainer(parseInt(numOfContainers.value),parseInt(numOfControls.value));
clearNodeData(containerWrapper);
//removeChild(containerWrapper);
console.timeEnd("Deleting controls");
console.time("populating controls");
controlsPerContainer.forEach(num=>{
let newControlContainer = document.createElement("div")
newControlContainer.classList.add("ctrlContainer");
for(let j=0;j<num;j++){
createControl(newControlContainer);
}
containerWrapper.appendChild(newControlContainer);
})
console.timeEnd("populating controls");
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
border: 0;
height:100%
}
.containerWrapper{
display:flex;
flex-direction: row;
height: 90%;
}
.ctrlContainer{
/* flex-grow:1; */
flex-shrink: 0;
border-style: solid;
border-width: 0.5px;
margin:0 2px;
flex-basis: calc(25% - 4px);
align-items: stretch;
display:flex;
flex-direction: column;
overflow: auto;
}
.ctrlWrapper{
border-style: solid;
border-width: .5px;
margin:2px
}
.ctrlHeading{
display:block;
width: 100%;
text-align: left;
border: 0;
}
.ctrl{
display:none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Controls</title>
<link rel="stylesheet" href="style/main.css">
</head>
<body>
<label for="numOfContainers">Number of Containers</label>
<input type="number" id="numOfContainers" name="numOfContainers" min="1" max="500" value="100">
<label for="numOfControls">Number of Controls</label>
<input type="number" id="numOfControls" name="numOfControls" min="1" max="1500" value="1500"><br>
<button id="create">Create</button>
<div class="containerWrapper">
<!-- <div class="ctrlContainer">
<div class="ctrlWrapper">
<button class="ctrlHeading">Checkbox</button>
<input class="ctrl" type="checkbox">
</div>
<div class="ctrlWrapper">
<button class="ctrlHeading">Checkbox</button>
<input class="ctrl" type="checkbox">
</div>
</div>
<div class="ctrlContainer">2</div>
<div class="ctrlContainer">3</div> -->
</div>
<script type="module" src="scripts/MainBackup.js"></script>
</body>
</html>
I tried analyzing using chrome developer tools and could see "append" function is taking more total time. Please let me know if I am doing something wrong in deleting or adding controls and how to avoid this time build up with every run.
More Information after some more exploration:
I am seeing this behavior only in chrome. In firefox and edge, there is no time buildup.
Firefox:
This occurs only in my system. Others are not able to replicate.
The time build-up occurs in portion of code in which I append inputs to the label to assign it to the input without using id. If I directly append the input to container, the time buildup doesn't happen

Related

how to reset color grid?

I am creating a simple etch-a-sketch game. currently on hover it colors in black. I am trying to use a button to reset the colors back to white. However, i can't get the button to function with an event listener, if i add an alert it displays the alert but nothing else. Please guide me and supply a documentation that I can reference as I want to learn and fixing it without explaining will be counterproductive at this point.
Thank you !
const containerGrid = document.getElementById("mainGrid");
function makeGrid(col) {
for (let i = 0; i < col * col; i++) {
const gridAdd = document.createElement("div");
gridAdd.classList.add("box");
gridAdd.textContent = "";
containerGrid.appendChild(gridAdd);
}
}
makeGrid(16); // make grid 16*16
const btnClear = document.getElementById("clear");
//mouseover event black - need to link to button (well done :)
const boxes = document.querySelectorAll('.box').forEach(item => {
item.addEventListener('mouseover', event => {
item.style.backgroundColor = "black";
})
});
btnClear.addEventListener("click", () => {
boxes.style.backgroundColor = "white";
});
const changeGrid = document.getElementById(".sizechange");
/*clearBtn.forEach.addEventListener("click", function () {
clearBtn.style.color ="white";
});
*/
/*const randomBtn = document.getElementById("randomgen").addEventListener('click',(e) => {
console.log(this.classname)
console.log(e.currentTarget === this)
}) */
//change color
#mainGrid {
display: grid;
justify-content: center;
align-items: center;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: auto;
margin-left: 150px;
width: 200px;
}
.box {
color: black;
border: 3px solid;
height: 10px;
width: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch-a-Sketch</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<div id="colorContainer">
<input type="radio" id="blackchoice" value="color" name="black" class="defaultbtn">
<label for="defaultcolor">black</label>
<input type="radio" id="randomgen" class="hey">
<label for="randomchoice">random</label>
</div>
<div id="changeGrid">
<button id="clear">clear</button>
</div>
<div id="mainGrid"></div>
<script src="app.js"></script>
</body>
</html>
A couple of related problems:
The variable boxes is undefined. It looks as though it was required to be the set elements with class box. When it is being defined this is indeed done, but then made undefined by the forEach attached to it. Separate out these two things and boxes will become the collection of all elements with class box.
Then when the clear is clicked you need to step through each of these boxes making their background color white, so again use a forEach.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch-a-Sketch</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
#mainGrid {
display: grid;
justify-content: center;
align-items: center;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: auto;
margin-left: 150px;
width: 200px;
}
.box {
color: black;
border: 3px solid;
height: 10px;
width: 10px;
}
</style>
</head>
<body>
<div id="colorContainer">
<input type="radio" id="blackchoice" value="color" name="black" class="defaultbtn">
<label for="defaultcolor">black</label>
<input type="radio" id="randomgen" class="hey">
<label for="randomchoice">random</label>
</div>
<div id="changeGrid">
<button id="clear">clear</button>
</div>
<div id="mainGrid"></div>
<script src="app.js"></script>
<script>
const containerGrid = document.getElementById("mainGrid");
function makeGrid(col) {
for (let i = 0; i < col * col; i++) {
const gridAdd = document.createElement("div");
gridAdd.classList.add("box");
gridAdd.textContent = "";
containerGrid.appendChild(gridAdd);
}
}
makeGrid(16); // make grid 16*16
const btnClear = document.getElementById("clear");
//mouseover event black - need to link to button (well done :)
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
box.addEventListener('mouseover', event => {
box.style.backgroundColor = "black";
})
});
btnClear.addEventListener("click", () => {
boxes.forEach(box => {
box.style.backgroundColor = "white";
});
});
const changeGrid = document.getElementById(".sizechange");
/*clearBtn.forEach.addEventListener("click", function () {
clearBtn.style.color ="white";
});
*/
/*const randomBtn = document.getElementById("randomgen").addEventListener('click',(e) => {
console.log(this.classname)
console.log(e.currentTarget === this)
}) */
//change color
</script>
</body>
</html>
Simplify your CSS
Use a SELECT element for your colors
Define the gridTemplateColumns in JS, not in CSS.
Use simpler functions
Use global variables to store the current grid size and color
Don't forget to clear your grid before changing the size
Assign the mouseenter Event on each cell on creation!
Add a boolean variable isPenDown for a better user experience!
const NewEL = (sel, prop) => Object.assign(document.createElement(sel), prop);
const EL_grid = document.querySelector("#grid");
const EL_clear = document.querySelector("#clear");
const EL_color = document.querySelector("[name=color]");
const EL_size = document.querySelector("[name=size]");
let size = parseInt(EL_size.value, 10);
let color = "black";
let isPenDown = false;
function makeGrid() {
EL_grid.innerHTML = ""; // Clear current grid!
for (let i = 0; i < size ** 2; i++) {
EL_grid.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
EL_grid.append(NewEL("div", {
className: "box",
onmousedown() { isPenDown = true; paint(this); },
onmouseup() { isPenDown = false; },
onmouseenter() { if (isPenDown) paint(this); },
}));
}
};
function paint(EL) {
EL.style.backgroundColor = color;
}
EL_clear.addEventListener("click", () => {
const tmp_color = color; // Remember current color
color = "transparent"; // Temporarily set it to transparent
EL_grid.querySelectorAll(".box").forEach(paint); // Paint all cells as transparent
color = tmp_color; //* Reset as it was before.
});
EL_color.addEventListener("change", () => {
color = EL_color.value;
if (color === "random") color = `hsl(${~~(Math.random() * 360)}, 80%, 50%)`;
});
EL_size.addEventListener("change", () => {
size = parseInt(EL_size.value, 10);
makeGrid();
});
// INIT!
makeGrid();
#grid {
display: inline-grid;
margin: 10px 0;
}
#grid .box {
border: 1px solid;
height: 10px;
width: 10px;
margin: 0;
user-select: none;
}
<div>
<label>
Size:
<input type="number" name="size" value="16">
</label>
<label>
Color:
<select name="color">
<option value="black">black</option>
<option value="white">white</option>
<option value="red">red</option>
<option value="yellow">yellow</option>
<option value="orange">orange</option>
<option value="fuchsia">fuchsia</option>
<option value="transparent">CLEAR (transparent)</option>
<option value="random">RANDOM COLOR</option>
</select>
</label>
<button id="clear">Clear canvas</button>
</div>
<div id="grid"></div>

refactoring javascript code to create for loop

I am practicing Javascript. I want each link to display something different in the DOM when clicked.
Here is my current Javascript that works.
//used a 'for' loop to hide each 'notes' page
const element = document.querySelectorAll(".notes");
for (let x = 0; x < element.length; x++)
element[x].style.display = 'none';
const html_link= document.getElementById('html-link');
const css_link = document.getElementById('css-link');
const javascript_link = document.getElementById('js-link');
const html_notes = document.getElementById('html-notes');
const css_notes = document.getElementById('css-notes');
const js_notes = document.getElementById('js-notes');
html_link.onclick = function() {
html_notes.style.display = "block";
css_notes.style.display = "none";
js_notes.style.display = "none";
}
css_link.onclick = function() {
css_notes.style.display = "block";
html_notes.style.display = "none";
js_notes.style.display = "none";
}
javascript_link.onclick = () => {
js_notes.style.display = "block";
html_notes.style.display = "none";
css_notes.style.display = "none";
}
How can I refactor it using a for loop? My thinking was for each link clicked, display notes. But I am struggling to figure out how to display the notes div correctly that matches the link clicked. This is what I have started.
const links = document.querySelectorAll('.links')
for (const link of links) {
link.addEventListener('click', function() {
let ref = event.target.parentElement.id.replace('link','notes');
//replaces parent element with id 'notes'
const show = document.getElementById(ref);
//'show' div with new id
})
}
Welcome, fellow newbie! I've taken the liberty of writing the html and very minimal styling as well. This is my first attempt at an answer on stackoverflow.
Please note some features of the code I've added:
'links' class added to all links.
'notes' class added to all notes.
'data-notes' attribute added to all links (with the id of each link's respective notes)
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0"/>
</head>
<body>
<div class="outer">
<div id="html-link" data-notes="html-notes" class="links">
<p>html-link</p>
</div>
<div id="css-link" data-notes="css-notes" class="links">
<p>css-link</p>
</div>
<div id="javascript-link" data-notes="javascript-notes" class="links">
<p>javascript-link</p>
</div>
</div>
<div class="outer">
<div id="html-notes" class="notes">
<p>html-notes</p>
</div>
<div id="css-notes" class="notes">
<p>css-notes</p>
</div>
<div id="javascript-notes" class="notes">
<p>javascript-notes</p>
</div>
</div>
<style>
.links {
cursor: pointer;
background: green;
color: white;
padding: 1rem;
margin: 1rem;
}
.notes {
display: none;
background: blue;
color: white;
padding: 1rem;
margin: 1rem;
}
.outer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
margin: 2rem 0;
}
</style>
<script>
const links = document.querySelectorAll('.links');
const notes = document.querySelectorAll('.notes');
for (const link of links) {
link.onclick = function () {
for (const note of notes) {
if (note.id == link.dataset.notes) {
note.style.display = "block";
} else {
note.style.display = "none";
}
}
}
}
</script>
</body>
</html>

Creating new object instances and pushing them to an array in plain Javascript [duplicate]

This question already has answers here:
JavaScript code to stop form submission
(14 answers)
Closed 2 years ago.
I'm trying to create a form that when submitted, creates a new object with the input values, and then stores that object in an array.
For some reason, the array is "resetting" and not saving the objects.
let myLibrary = []
function Book(title,author,pages,read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
myLibrary.push(this)
}
function checkForm(){
let name = document.querySelector('input[name="title"]').value
let author = document.querySelector('input[name="author"]').value
let pages = document.querySelector('input[name="pages"]').value
let read = document.querySelector('input[name="read"]').checked
new Book(name,author,pages,read)
document.getElementById('library').innerText = JSON.stringify(myLibrary)
}
const submit = document.getElementById('btn1')
submit.addEventListener("click",checkForm);
<input name='title' />
<input name='author' />
<input name='pages' />
<input name='read' />
<button id='btn1'>Click me! </button>
<div >Library:</div>
<div id='library'></div>
You are listening for a click event on the submit button, however the submit button also submits the form. Forms will naturally cause a refresh if the default "submit" event is not prevented.
Instead you could listen to your forms submit event and prevent it:
// Query select the form and
form.addEventListener('submit', function(e){
e.preventDefault();
checkForm();
});
As you have a form in your html, you'll have to prevent its default submission event which results in a reload of the page with preventDefault(). You could, for example, change your checkForm() and add the e.preventDefault() there to prevent the form from being submitted.
let myLibrary = []
function Book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
}
function addtoLibrary(title, author, pages, read) {
let book = new Book(title, author, pages, read)
myLibrary.push(book)
}
let table = document.querySelector(".table");
myLibrary.forEach(function(e) {
table.innerHTML += `<tr><td>${e.title}</td>
<td>${e.author}</td>
<td>${e.pages}</td>
<td>${e.read}</td>
</tr>
`
});
// Selectors
let add = document.querySelector("#add")
let submit = document.querySelector("#submit")
function checkForm(e) {
e.preventDefault(); // prevent the form from being submitted
let name = document.querySelector('input[name="title"]').value
let author = document.querySelector('input[name="author"]').value
let pages = document.querySelector('input[name="pages"]').value
let read = document.querySelector('input[name="read"]').checked
addtoLibrary(name, author, pages, read)
console.log(myLibrary);
}
submit.addEventListener("click", checkForm);
html,
body {
height: 100%;
}
* {
font-family: Graphik Regular;
}
ul {
list-style-type: none;
}
table,
th,
td {
border-collapse: collapse;
text-align: left;
border: 1px solid black;
}
table {
width: 100%;
}
td,
th {
height: 50px;
padding: 10px;
width: 200px;
min-width: 100px;
}
th {
background-color: gray;
margin-bottom: 50px;
}
.headers {
margin-bottom: 5px;
}
button {
background-color: #4CAF50;
/* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin-top: 30px;
}
.pop-container {
text-align: center;
/* display: none;*/
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}
form {
background-color: gray;
}
input {
font-size: 20px;
width: 300px;
margin-bottom: 5px;
}
<!DOCTYPE html>
<meta charset="UTF-8">
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="style.css">
</stylesheet>
<script type="text/javascript" src="http://livejs.com/live.js"></script>
</head>
<body>
<div class="pop-container">
<form id="bookquery">
<input type="text" name="title" id="title" placeholder="Title"></br>
<input type="text" name="author" placeholder="Author"></br>
<input type="text" name="pages" placeholder="Pages"></br>
<p>Have you read it?<input type="checkbox" placeholder="Title" name="read"></p>
</br>
<button id="submit">Submit</button>
</form>
</div>
<table class="headers">
<th>Title</th>
<th>Author</th>
<th>Pages</th>
<th>Read</th>
</table>
<table class="table tstyle">
</table>
<button id="add">Add new book</button>
<script src="script.js"></script>
</body>
</html>
function checkForm(e) {
e.preventDefault(); // prevent the form from being submitted
let name = document.querySelector('input[name="title"]').value
let author = document.querySelector('input[name="author"]').value
let pages = document.querySelector('input[name="pages"]').value
let read = document.querySelector('input[name="read"]').checked
addtoLibrary(name, author, pages, read)
}
The above answers didn't quite work for me so here is a simplified, fully working example. As a general guide to getting things like this to work I always try to simplify as much as possible.
index.html
<html>
<header></header>
<body>
<div>
<form id="myForm">
<label for="title">title:</label><br>
<input type="text" id="title" name="title" value="title"><br>
<button id="submit">Submit</button>
</form>
</div>
<script type="text/javascript" src="functions.js"></script>
</body>
</html>
functions.html
let myLibrary = [];
function Book(title) {
this.title = title;
myLibrary.push(this);
}
function checkForm(){
let title = document.querySelector('input[name="title"]').value;
new Book(title);
myLibrary.forEach(function(element) {
console.log(element);
});
}
document.getElementById("myForm").addEventListener(
'submit',
function(e) {
e.preventDefault();
checkForm();
}
);
I'll leave it to you to add back in the other fields on the Book object.
I am not sure because I've tried to illustrate that your code actually stores the object. It's either that your form refreshes the page... that might be the cause but as far as the code you've provided is concerned, everything works as expected.
let myLibrary = []
function Book(title,author,pages,read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
myLibrary.push(this)
}
function checkForm(name,author,pages,read)
{
new Book(name,author,pages,read)
}
checkForm("Chris","Jerry","56","65");
checkForm("Sean","John","56","65");
// Both Objects are still stored...
console.log(myLibrary);

How to create element within for loop in Javascript

I have one text box and one button in my code. I want to let the user to enter a text and click the button. If so a card will be created with the user entered text, and the background color of the card will be set using a json file.
But in my code if the user clicks the button for the second time, previously created card disappears and a new card is being created leaving the space of previously created card. But I want all the cards to be aligned one below one.
I think this can be done using a loop function by setting different ids to each card. Unfortunately I am not able to do it properly.
I am attaching my code here, please someone help me with this.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Task</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<link rel = "stylesheet" href = "css/style.css" type = "text/css">
</head>
<body>
<h2>Creative Handle Task Assignment</h2>
<input type="text" name="text" id="text" placeholder="Enter your text here...">
<button id="btn">Click</button>
<div class="flex-container" id="container">
</div>
<script src="js/custom_script.js" type="text/javascript"></script>
</body>
</html>
style.css
.flex-container {
display: flex;
flex-direction: column-reverse;
justify-content: center;
align-items: center;
}
.flex-container > div {
/*background-color: DodgerBlue;*/
color: white;
width: 100px;
margin: 10px;
text-align: center;
line-height: 75px;
font-size: 30px;
}
custom_script.js
const subBtn = document.getElementById("btn");
const inptTxt = document.getElementById("text");
const contDiv = document.getElementById("container");
subBtn.disabled = true
inptTxt.addEventListener('input', evt => {
const value = inptTxt.value.trim()
if (value) {
inptTxt.dataset.state = 'valid'
subBtn.disabled = false
} else {
inptTxt.dataset.state = 'invalid'
subBtn.disabled = true
}
})
var xhttp = new XMLHttpRequest();
subBtn.addEventListener("click",function(){
var crd = document.createElement("div");
crd.setAttribute("id", "card");
crd.innerHTML = inptTxt.value;
contDiv .appendChild(crd);
xhttp.onreadystatechange=function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("card").style.background = JSON.parse(this.responseText).color;
}
};
xhttp.open("GET","http://api.creativehandles.com/getRandomColor","" ,true);
xhttp.send();
})
Each time you create a new element you give it the id of card. You can't have multiple html elements with the same id. You should use crd.setAttribute("class", "card");' instead. The external stylesheet you load has styling for the class .card but not for id #card.
You can not give id to more one html tag.
Instead of id use class attribute i.e.
crd.setAttribute("class", "card");

Method fired multiple times on click event

I'm building a web app in which the user can type in any key word or statement and get in return twenty results from wikipedia using the wikipedia API. AJAX works just fine. When the web app pulls data from wikipedia it should display each result in a DIV created dynamically.
What happens is that, when the click event is fired, the twenty DIVs are created five times, so one hundred in total. I don't know why but, as you can see in the snippet below, the web app creates twenty DIVs for each DOM element that has been hidden (through .hide) when the click event is fired.
Here's is the code:
function main() {
function positive() {
var bar = document.getElementById("sb").childNodes[1];
var value = bar.value;
if (!value) {
window.alert("Type in anything to start the research");
} else {
var ex = /\s+/g;
var space_count = value.match(ex);
if (space_count == null) {
var new_text = value;
} else {
new_text = value.replace(ex, "%20");
//console.log(new_text);
}
url = "https://en.wikipedia.org/w/api.php?action=query&format=json&prop=&list=search&continue=-%7C%7C&srsearch=" + new_text + "&srlimit=20&sroffset=20&srprop=snippet&origin=*";
var request = new XMLHttpRequest();
request.open("GET", url);
//request.setRequestHeader("Api-User-Agent", "Example/1.0");
request.onload = function() {
var data = JSON.parse(request.responseText);
render(data);
//console.log(data);
}
request.send();
}
}
function render(data) {
$("#first_h1, #first_h3, #sb label, #second_h1, #second_h3").hide("slow", function() {
$("#sb input").css({
"float":"left",
"margin-left":"130px"
});
$("#first_btn").css({
"float":"left"
});
var title = data.query.search[0].title;
var new_text = document.createTextNode(title);
var new_window = document.createElement("div");
new_window.appendChild(new_text);
new_window.setAttribute("class", "window");
var position = document.getElementsByTagName("body")[0];
position.appendChild(new_window);
//}
});
}
var first_btn = document.getElementById("first_btn");
first_btn.addEventListener("click", positive, false);
}
$(document).ready(main);
html {
font-size: 16px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;รน
}
.align {
text-align: center;
}
#first_h1 {
margin-top: 30px;
}
#first_h3 {
margin-bottom: 30px;
}
#sb {
margin-bottom: 10px;
}
#second_h1 {
margin-top: 30px;
}
#second_h3 {
margin-bottom: 30px;
}
.window {
width: 70%;
height: 150px;
border: 3px solid black;
margin: 0 auto;
margin-top: 20px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Wikipedia Viewer</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/main.css">
</head>
<body>
<h1 class="align" id="first_h1">Wikipedia Viewer</h1>
<h3 class="align" id="first_h3">Type in a key word about the topic you are after<br>and see what Wkipedia has for you..</h3>
<p class="align" id="sb">
<input type="text" name="search_box" placeholder="Write here">
<label for="search_box">Your search starts here...</label>
</p>
<p class="align" id="first_btn">
<input type="submit" value="SEND">
</p>
<h1 class="align" id="second_h1">...Or...</h1>
<h3 class="align" id="second_h3">If you just feel eager of random knowledge,<br>punch the button below and see what's next for you...</h3>
<p class="align" id="second_btn">
<input type="submit" value="Enjoy!">
</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script src="js/jquery-3.2.1.min.js"><\/script>')
</script>
<script type="text/javascript" src="js/script.js"></script>
</body>
</html>
I made the code easier to read by erasing the for loop. As you can see, even with just one result, it is displayed five times.
Do you know guys why it happens?
thanks
The line:
$("#first_h1, #first_h3, #sb label, #second_h1, #second_h3").hide("slow", function() {})
Says, for every element in this "list", hide the element and run this block of code after hidden.
This code is the culprit:
$("#first_h1, #first_h3, #sb label, #second_h1, #second_h3").hide("slow",
function() {...});
The callback function is called five times, one for each ID listed, not once for all of them, as you might expect.
A workaround is to create a class (say, "hideme"), apply it to each element you want to hide, and write:
$('.hideme').hide("slow", function() {...});
function render(data) {
$("#first_h1, #first_h3, #sb label, #second_h1, #second_h3").hide("slow", function() {
$("#sb input").css({
"float":"left",
"margin-left":"130px"
});
$("#first_btn").css({
"float":"left"
});
}); // Finish it here..
var title = data.query.search[0].title;
var new_text = document.createTextNode(title);
var new_window = document.createElement("div");
new_window.appendChild(new_text);
new_window.setAttribute("class", "window");
var position = document.getElementsByTagName("body")[0];
position.appendChild(new_window);
//}
// }); Move this line..
}
As described in the docs:
complete: A function to call once the animation is complete, called once per matched element.
Which means this line will call the handle function 5 times with 5 matched elements.
$("#first_h1, #first_h3, #sb label, #second_h1, #second_h3").hide("slow", function() {
The easiest solution is moving the render codes outside of the hide event handler

Categories