How to create element within for loop in Javascript - 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");

Related

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

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

Add Show Dialog custom html to Google Slides Script

I'm trying to make this dialog popup for the duration of the execution of the AddConclusionSlide function, but I get the exception: "TypeError: Cannot find function show in object Presentation." Is there an alternative to "show" for Google Slides Script (This works perfectly in google docs)?
function AddConclusionSlide() {
htmlApp("","");
var srcId = "1Ar9GnT8xPI3ZYum9uko_2yTm9LOp7YX3mzLCn3hDjuc";
var srcPage = 6;
var srcSlide = SlidesApp.openById(srcId);
var dstSlide = SlidesApp.getActivePresentation();
var copySlide = srcSlide.getSlides()[srcPage - 1];
dstSlide.appendSlide(copySlide);
Utilities.sleep(3000); // change this value to show the "Running script, please wait.." HTML window for longer time.
htmlApp("Finished!","");
Utilities.sleep(3000); // change this value to show the "Finished! This window will close automatically. HTML window for longer time.
htmlApp("","close"); // Automatically closes the HTML window.
}
function htmlApp (status,close) {
var ss = SlidesApp.getActivePresentation();
var htmlApp = HtmlService.createTemplateFromFile("html");
htmlApp.data = status;
htmlApp.close = close;
ss.show(htmlApp.evaluate()
.setWidth(300)
.setHeight(200));
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
img {
display: block;
margin-left: auto;
margin-right: auto;
width: 25%;
}
.gap-10 {
width: 100%;
height: 20px;
}
.gap-20 {
width: 100%;
height: 40px;
}
.gap-30 {
width: 100%;
height: 60px;
}
</style>
</head>
<body>
<div class="container">
<div>
<p align="justify" style="font-family:helvetica,garamond,serif;font-size:12px;font-style:regular;" class="light">
Function is running... This could take a while. It's a lot of data...</p>
</div>
<p id="status">(innerHTML).</p>
<div id="imageico"></div>
<script>
var imageContainer = document.getElementById("imageico");
if (<?= data ?> != "Finished!"){
document.getElementById("status").innerHTML = "";
} else {
document.getElementById("status").innerHTML = "";
}
if (<?= close ?> == "close"){
google.script.host.close();
}
</script>
</body>
</html>
Unlike Spreadsheet object, Slide object doesn't have a show method. So, class ui needs to be used:
SlidesApp.getUi().showModalDialog(htmlApp.evaluate()
.setWidth(300)
.setHeight(200), "My App")

click function not working on class

I wanted to take input from user in a text area and then parse those sentences in an array and then make each sentence clikcable so that clicked sentence should get copied to another text box. So to achieve this I tried creating span elements in the text area and each span would have class sentence so that on click it would copy that sentence to another text area. But the innerHTML wont produce the spans. Instead it plainly writes it in the textarea as a string.
document.getElementById("go").onclick = function() {
var lines = $('#input').val().split(".");
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var para = document.getElementById("output");
var htmlButton = ' <span class="sentence">' + line + "</span>";
para.innerHTML = para.innerHTML + htmlButton;
}
};
$('.sentence').click(function(e) {
console.log("%00");
var sentence = $(this).text();
$('#textplace').html(sentence);
});
#input {
height: 150px;
font-family: "Courier New", Courier, monospace;
}
.sentence {
font-family: "Courier New", Courier, monospace;
}
body {
margin: 25px;
}
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<div class="alert alert-info" role="alert">Enter multiple lines of things here and it will be converted to a Javascript array format.</div>
<textarea id="input" class="u-full-width" placeholder=""></textarea>
<div id="output"></div>
<input id="go" class="button-primary" type="submit" value="Go!">
<textarea id="textplace"></textarea>
</body>
</html>
After changing the textarea to division, the innerHTML works but the click function on class sentence does not work.
In line number 7 you have done a mistake change
//para.innerHTMl
para.value
Why because you are trying to access the textarea element which is having a attribute value not innerHTML to get the data. You can use innerHTML for 'div','p' tags etc.

Broken Javascript Code

I'm trying to create a website where three things happen, but I am stuck.
(1) When the button “ADD” button is clicked it will create a new paragraph
and add it to the output. The contents of the paragraph should come from the text area that is below the [ADD] button.
(2) If the “delete” button is pressed I need to delete the first paragraph in the div.
(3) If the user tries to delete when there are no paragraphs, create an “alert" that says:"No Paragraphs to delete".
I got my JS to put each paragraph into the div, but I'm not really sure how to delete it... Any help would be much appreciated.
window.onload = function() {
var button = document.getElementById("add");
button.onclick = insertItem;
}
function insertItem() {
var added = document.getElementById("output");
var textToAdd = document.getElementById("input");
if (textToAdd.value != "") {
var newp = document.createElement("p");
newp.innerHTML = textToAdd.value;
added.appendChild(newp);
}
}
var deletebutton = document.getElementsByTagName("delete");
deletebutton.onclick = deleteItem;
function deleteItem() {
var output = document.getElementById("output");
var pars = output.getElementsByTagName("p");
if (pars.length > 0) {
output.removeChild(pars[0]);
}
}
#output {
border: blue 5px solid;
padding: 10px;
margin-bottom: 10px;
margin-top: 10px;
width: 50%;
}
#output p {
padding: 10px;
border: black 1px dashed;
}
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js" type="text/javascript"></script>
<script src="task3.js"></script>
</head>
<body>
<h2> TASK 3 - Creating, Appending and Deleting Nodes in the DOM Tree </h2>
<p> Type in text below, click add to add as paragraph. <button id="add"> Add </button> </p>
<textarea id="input" rows="10" cols="60">
</textarea><br>
<button id="delete">Delete Last Paragraph</button>
<br><br>
<h2> Added Paragraphs </h2>
<div id="output">
</div>
</body>
</html>
You're fetching the delete button wrong. You're using getElementsByTagName instead of by id.
When deleting, you will probably delete the first <p> you have in your markup that doesnt belong to your output. To fix this you could simply fetch all children of your output div and remove the first one:
function deleteItem() {
let output = document.getElementById('output')
if (output.hasChildNodes()) {
let outputs = output.childNodes
outputs[0].remove()
}
}

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