Prevent paragraph from adding new line (JAVASCRIPT, HTML) - javascript

I have a program I'm writing that will display countries and sub-county via an array of information. I've decided to include a part where instead of displaying in a text area, I just want it to display via paragraph output.
However, if the user clicks the button again, it will keep copying and pasting the output. I want to prevent this in case the user does this action
[Current Result after button is pressed multiple times ][1] https://i.stack.imgur.com/enZVW.png
It displays the result multiple times if the button is clicked again.
[How I want it to look like after pressing the button multiple times][2] https://i.stack.imgur.com/dXqYE.png
HTML
<input type="input" id="city"><br><br>
<button id="button2"> <!-- Giving button an ID to be called out in our init function and add an eventlistener -->
Show country/subcountry</button><br><br><br>
<!-- <textarea readonly id="countryOut" style="overflow-y:scroll;
resize: none; margin-left: 2.7em; " ></textarea><br><br> -->
<p id = "countryOut"></p><br><br>
JAVASCRIPT
// display += `${sub}, ${co}\n \n`; // display subcountry, and country with new lines included for spacing
p2.innerHTML += `${sub}, ${co}\n \n`;
}
}
}
function init() {
var button = document.getElementById("button1"); // When country is entered, cities will display
button.addEventListener("click", getCountrySub); // when click event/action is performed, the function of getCountry will execute
var button2 = document.getElementById("button2"); // when city is entered, the region, country, sub country, etc. will display
button2.addEventListener("click", getCities); // when click event/action is performed, the function of getCities will execute
}```

+= sign is making duplicated texts.
Fix this to = will work what you intended.
// AS-IS
p2.innerHTML += `${sub}, ${co}`
// TO-BE
p2.innerHTML = `${sub}, ${co}`

Feels like the code is incomplete, assuming that this is a loop that iterates through both lists
p2.innerHTML += `${sub}, ${co}`
Then I think you are missing a cleanup before you start the output, so before the loops start try this:
p2.innerHTML = ""; // trick is here, when button is clicked clear old results, then show new information
for (const co of countries) { // Please fix to your variable names
for (const sub of co.sub) {
p2.innerHTML += `${sub}, ${co}`;
}
}

Related

Make For Loop pause until click on div - JavaScript

I'm trying to write a code that displays several boxes one after the other, but only after the previous one has been closed. This can be done in two ways. Either the box closes automatically after 10 seconds, or it stays up indefinitely until it is closed by clicking "X".
I am trying to use a for loop to iterate over an array (mandatory) of these boxes, but I cannot work out how to 'pause' the loop to wait for user action. The loop must stop when all boxes have been displayed.
Does anyone know how this could be done (without jQuery)?
I've tried using setTimeout, but then realized it cannot be done this way. I'm new to programming, so it's all a bit confusing, if anyone could help I'd really appreciate it!
It may be worth mentioning that I'd prefer not to use id's.
I've tried to simplify my code to be easier to read:
HTML:
// Simplified - every element has this structure, only the class changes for the parent div
<div class=" box 'type' "> // type -> can be '"success" or "warning"
// BOX BODY
<div class="box-close" onClick="removeBox()"> X </div>
</div>
CSS
.box{display="none";}
JavaScript
// Simplified - each box div present in page is stored in array allBoxes
allBoxes = array of boxes
//Show boxes 1 by 1
for (j = 0; j < allBoxes.length; j++) {
showBox(allBoxes[j]);
}
function showBox() {
box=allBoxes[j];
box.style.display= "block";
if (box.classList.contains("success")==true){
setTimeout(removeBox, 10000); //PROBLEM: only executes after for loop is done, meaning it 'removes' the last div in the array being looped, regardless of type class
//alternative
setTimeout(removeBox(box), 10000); //Problem: executes remove immediately, without waiting the 10s
}
else{
//something to make the For Loop pause until the user clicks on X
box.querySelector(".box-close").addEventListener("click",removeBox); //doesn't do anything, loop continues
//alternative
box.querySelector(".box-close").addEventListener("click",removeBox(box)); //simply removes box immediately (not in response to click or anything), loop continues
}
}
function removeBox() {
box.style.display = "none";
}
My take on this is to actually use setTimeout(). We can assign an onClick next to the timeout that both will show the next box. If needed, the timeout can be canceled using clearTimeout()
So the next box will be shown after 3 seconds, or when the previous box is closed (clicked in my demo below)
To give an example, please see the demo below, were we have 3 main functions:
openBox; opens a box, starts the timeout, set click event to toggle box
closeBox; closes a box
openNext; Call closeBox for current box, clear any timeout's that are set and ofc call openBox to open the next one
Please see additional explanation in the code itself.
const nBoxes = 5; // Number of boxes
let index = 0; // Current box index
let count = null; // setTimeout pid
// Function to open boxes, assign onClick and start the count-down
const openBox = (n) => {
var e = document.getElementById('box_' + n);
e.style.display = 'block';
e.onclick = openNext;
count = setTimeout(openNext, 3000);
}
// Function to close a box
const closeBox = (n) => document.getElementById('box_' + n).style.display = 'none';
// Function to cycle to the next box
const openNext = () => {
// Close current open box
if (index > 0) {
closeBox(index);
}
// Stop any count-downs
if (count) {
clearTimeout(count);
count = null;
}
// Stop the loop if we've reached the last box
if (index >= nBoxes) {
console.log('Done!')
return;
}
// Bump index and open new box
index++;
openBox(index);
};
// Start; open first box
openNext()
.box {
display: none;
}
<div id='box_1' class='box'>1</div>
<div id='box_2' class='box'>2</div>
<div id='box_3' class='box'>3</div>
<div id='box_4' class='box'>4</div>
<div id='box_5' class='box'>5</div>
The only thing you need the loop for is to assign the click event listener to the close buttons. Inside the click event listener we hide the current box and then find the next box and show it, if it exists.
Note: In the following snippet, the .box-wrapper element is necessary to isolate all the boxes from any other siblings so that box.nextElementSibling will properly return null when there are no more boxes left to open.
const autoBoxAdvanceTime = 10000
const allBoxes = document.querySelectorAll('.box')
allBoxes.forEach(box => box.querySelector('.box-close').addEventListener('click', () => nextBox(box)))
//Show first box
showBox(allBoxes[0]);
function showBox(box) {
box.style.display = "block";
if (box.classList.contains("success")) {
console.log(`Going to next box in ${autoBoxAdvanceTime/1000} seconds`)
setTimeout(() => {
// only advance automaticaly if the box is still showing
if (box.style.display === "block")
nextBox(box)
}, autoBoxAdvanceTime);
}
}
function nextBox(box) {
box.style.display = "none"
const next = box.nextElementSibling
if (next) {
console.log('going to box:', next.textContent)
showBox(next)
} else {
console.log('last box closed')
}
}
.box,
.not-a-box {
display: none;
}
.box-close {
cursor: pointer;
}
<div class="box-wrapper">
<div class=" box type success">
one
<div class="box-close"> X </div>
</div>
<div class=" box type ">
two
<div class="box-close"> X </div>
</div>
<div class=" box type ">
three
<div class="box-close"> X </div>
</div>
</div>
<div class="not-a-box">I'm not thier brother, don't involve me in this!</div>
Instead of a real loop, use a function that re-invokes itself after a delay.
showBox should not take the box index as an argument
Instead, it should look at the allBoxes array directly. You might also need to keep track of which boxes have already been dealt with, which could be done either by maintaining a second list of boxes, or with a simple counter; either way, you'd define that variable outside the function so that it would retain its value across invocations of showBox.
showBox should call itself as its final step
Instead of relying on the loop to call showBox, have showBox do that itself. (This is known as "tail recursion" -- a function that calls itself at its own end.)
To add a 6-second delay, you'd wrap that invocation in a setTimeout.
nextBoxTimer = setTimeout(showBox, 6000)
integrate the tail-recursing loop with manual box closing
When a user manually closes a box, you want to stop the current timer and start a new one. Otherwise, a person could wait 5 seconds to close the first box, and then the second box would automatically close 1 second later.
So, make showBox begin by canceling the current timer. This will be pointless when showBox is running because it called itself, but it's crucial in cases when showBox is running because the user pre-empted the timer by closing the previous box herself.
For this to work, you'll need to define the variable outside of showBox (which is why the snippet in step 2 doesn't use let when assigning into nextBoxTimer).

display checkbox input as text in a row created with JS, and delete that row

I have two problems with my vanilla JS exercise. I looked around the web and tried different code for two days now, and i'm honestly at a loss.
First, i have the checkbox input, where the user checks/unchecks a field. What i want is to have it written "yes" or "no" in a specific row field on webpage. The row gets created, but no matter what i try, it always writes "true/false" instead of what i want.
Input:
<label id="readlabel" for="read">Have you consumed the knowledge inside?</label><br>
<input type="checkbox" id="read" name="read">
checkbox gets created and appended to the table (id=myForm, table, tbody) solely with JS.
let book = {
...
read:document.getElementById("read").checked,
}
books.map((book) => {
...
let td5 = document.createElement("td");
td5.classList.add("checkTd");
td5.innerHTML = book.read;
row.appendChild(td5);
tbody.appendChild(row);
}
and the whole thing gets saved to local storage:
var existingBooks = JSON.parse(localStorage.getItem("BookList"));
if(existingBooks == null) existingBooks = [];
existingBooks.push(book);
localStorage.setItem("BookList", JSON.stringify(existingBooks));
books.splice(0,1);
Next up, there is the problem of deleting table rows. Button gets created and appended with JS:
books.map((book) => {
...
var td6 = document.createElement("button");
td6.innerHTML = "Delete";
td6.classList.add("deleteBtn");
row.appendChild(td6);
tbody.appendChild(row);
}
This button should delete the row of six cells on which the button is on. Again been trying different code for days with not a single one working. I hope the amount of code i pasted is enough.
Oh, and the code is organized as:
let books = []
const addBook =(ev)=> {
let book = {...}
books.push(book);
books.map((book) => {
"create and append"
})
save to storage
}
To delete row removeChild() DOM method can be used like this: elementToDelete.parentNode.removeChild(elementToDelete).
The only challenge it to navigate from Delete button node (event.currentTarget in event handler) to the row node that needs deleting (in a reliable way).
There is already a row variable so it can be used instead. Function arguments can be pre-filled with .bind() method, so current value of row can be passed to click handler.
Add click event handler to Delete buttons:
// the arguments get pre-filled using bind(null, row)
// this will be `null`
// row will be set
// event is automatically added by DOM when buttons
// are clicked and this function gets called
function deleteBtnHandler(row, event) {
event.preventDefault();
event.stopPropagation();
const elementToDelete = row;
// alternatively
// event.currentTarget (which is a Delete button)
// can be used to find a node to delete
// (add extra '.parentNode' to reach the needed element,
// needs to be updated when HTML tree structure changes)
// const elementToDelete = event.currentTarget.parentNode;
elementToDelete.parentNode.removeChild(elementToDelete);
}
...
var td6 = document.createElement("button");
td6.innerHTML = "Delete";
td6.classList.add("deleteBtn");
td6.addEventListener('click', deleteBtnHandler.bind(null, row));
row.appendChild(td6);
tbody.appendChild(row);

jQuery: A button that checks all checkboxes in its own DIV and unchecks all the others

I have a dynamically generated form with groups of checkboxes representing categories of companies. These eventually get plotted on a dynamic chart (not shown here). Each group of companies is in a div, and each div has a button called Only that should check all the checkboxes in its own category (div) and uncheck all the other checkboxes on the page.
Here's a Fiddle with all the code: https://jsfiddle.net/c2kn78a9/
The Only buttons have this code in them:
// Uncheck all checkboxes outside this div
$(this).closest("div").not(this).find('input[type=checkbox]').prop('checked', false).change();
// Check all checkboxes in this div
$(this).closest("div").find('input[type=checkbox]').prop('checked', true).change();
But it's not working. Any idea how to fix this?
Here's the code for the entire page.
<!-- This button is different than the other buttons -->
<button class="button-text" id="customize-button">Open User Settings</button>
<!-- Placeholder for dynamic form -->
<div id="company-selection-form"></div>
<script type="text/javascript">
function toMachineString(humanString) {
var machineString = humanString.replace(/\s+/g, '-').toLowerCase();
machineString = machineString.replace('&','');
return machineString;
}
// Setup the form
var categories = new Map([
['Tech Giants',['Alphabet','Amazon','Apple','Facebook','Microsoft']],
['Handset Manufacturers',['Apple','Samsung','Motorola','Sony']],
['Semiconductors', ['AMD','Intel','Nvidia']]
// ... more ...
]);
// Build company selection form inputs
let companySelectionHTML = '';
for (let category of categories) {
categoryName = category[0];
categoryList = category[1];
// Setup a div to differentiate each category of companies.
// Will be used for turning on/off categories en masse
companySelectionHTML += `<div id="${toMachineString(categoryName)}">\n`;
// Category heading
companySelectionHTML += `<h4>${categoryName}</h4>\n`;
// Only button
companySelectionHTML += `<button class="only" id="btn-only-${toMachineString(categoryName)}">Only</button>\n`;
categoryList.forEach(companyName => {
companySelectionHTML += `
<label class="checkbox-label">
<input id="x-${toMachineString(companyName)}" class="checkbox" type="checkbox" name="company" value="${companyName}" checked>
<label for="x-${toMachineString(companyName)}">${companyName}</label>
</label>`;
});
companySelectionHTML += '</div>\n</div>\n</div>\n';
}
// Append to DOM
const companySelectionId = document.getElementById('company-selection-form');
companySelectionId.insertAdjacentHTML('beforeend', companySelectionHTML);
// Make the ONLY buttons check all the checkboxes in their div and uncheck everything else
$(document).ready(function() {
$(document).on("click", ".only", function() {
// Uncheck all checkboxes outside this div
$(this).closest("div").not(this).find('input[type=checkbox]').prop('checked', false).change();
// Check all checkboxes in this div
$(this).closest("div").find('input[type=checkbox]').prop('checked', true).change();
});
});
</script>
Thanks!
Your .not(this) is trying to filter out the button element from the single closest div. You need to get all div's on the page and remove the closest div to "this" button.
From your JSFiddle like this:
var temp = $(this).closest("div");
$("div").not(temp).find('input[type=checkbox]').prop('checked', false).change();
OR (to avoid a new variable)
$("div").not($(this).closest("div")).find('input[type=checkbox]').prop('checked', false).change();
Matt G's solution works fine, it deselects all the checkboxes on the page.
I'd suggest to further refine it by first narrowing the selection to only your #company-selection-form
`$("#company-selection-form")
.find("div")
.not($(this)
.closest("div"))
.find('input[type=checkbox]')
.prop('checked', false)
.change();`
Nevertheless, allow me to suggest that you're maybe wasting your time learning this stuff. This programming paradigm is too problematic and anachronistic. It's slow, gets out of hand very quickly, and never brings anything but suffering. Even the slightest update to the UI can force you to revisit (after months sometimes), debug, and rewrite your code. It's never testable, no one would even bother to test this rigorously.
I mean, if your employer holds a gun to your head every day and you have to choose either to do it this way or die, you'd soon choose to die over this ordeal.

inputting grades (javascript function)

How do I make it so that the numbers I input are all on top of one another and another together in one spacing.
It supposed to show all the 3 grades I have input and when the button is clicked it should show the average and either pass or fail (I know it's supposed to be an if else statement but I really can't comprehend the codes and where will I put it)
If someone helps me solve this one can someone give me more user input / function related exercises that I can work on? thanks.
var p = prompt ("enter first grade");
var c = prompt ("enter second");
var o = prompt ("enter third grade");
document.write (p);
document.write (c);
document.write (o);
function xxx (p,c,o)
{
document.getElementById("demo4").innerHTML = ((parseInt(p)+parseInt(c)+parseInt(o)) / 3)
}
<p id="demo"></p>
<p id="demo2"></p>
<p id="demo3"></p>
<button onclick="xxx()">calculate</button>
<p id="demo4"></p>
<p id="demo5"></p>
Let me start first with the main non-logic problems with your code:
You shouldn't use prompt, it's just bad user experience. Dynamically create HTML elements instead.
You shouldn't use document.write. You aren't able to specify where the text should go and it makes your code vulnerable towards XSS vulnerabilities.
You shouldn't use onclick. You should never mix the JS with your HTML like that. (React's HTML-like syntax is not HTML, it's JSX. It's okay to do that there.)
Now, back to the main logic which your code should follow.
Provide the user with one <input type='number'> field and an "Add" button.
Recalculate the result on every change, don't rely on a "Calculate" button to update your state.
You can use an if statement to detect a failing grade.
Here is an example of a more proper implementation of what you're trying to accomplish. I know I'm basically doing the homework task for you so I would like you to learn from this.
// Storing references to elements.
const grades = document.getElementById('grades');
const template = document.getElementById('template');
const addButton = document.getElementById('add');
const averageOut = document.getElementById('average');
const failedOut = document.getElementById('failed');
function recalculate() {
// Let's create a sum variable.
let sum = 0;
// Let's query the document for grade input fields.
const numberFields = document.querySelectorAll('#grades input');
// Iterate over number fields with for ... of.
for (let field of numberFields) {
// Unary + converts the value into a number.
// parseInt can be used instead as well.
sum += +field.value;
}
// .length gives you the total amount of input fields.
// .length works on any array and some lists. (querySelectorAll returns a NodeList instead of an array)
const average = sum/numberFields.length;
// Use innerText to prevent XSS.
averageOut.innerText = average;
// If statement to check if the person has failed.
if (average < 3.0) {
failedOut.innerText = 'FAIL';
} else {
failedOut.innerText = 'NOT FAIL';
}
}
// Let's recalculate the average on any change made in ANY field in the document.
// This is basically what jQuery does in its '.on(eventType, selector, listener)' method.
// This technique relies on event bubbling.
// Make sure to replace this once you start adding more functions into the document.
// Also, () => is a closure in JavaScript, which is a function but with a different context.
document.addEventListener('change', () => recalculate());
// Adding new fields.
addButton.addEventListener('click', () => {
// Clone the template...
const templateClone = template.cloneNode(true);
// ...and append it to our grades.
grades.appendChild(templateClone);
// Also recalculate.
recalculate();
});
// Recalculate on load.
recalculate();
<ul id="grades">
<li id="template">
<input type="number" value="1" min="1" max="5" />
</li>
</ul>
<button id="add">Add</button>
<div>
<span>Average:</span> <span id="average"></span>
</div>
<div>
<span>Failed?:</span> <span id="failed"></span>
</div>
What you could improve upon is adding a "Remove" button.

Using nodejs/electron to add new line causing multiple lines to be added

My code is having a strange behavior. So I have a check box, and when ever the file is checked, it add a new line with new text
checkbox.addEventListener('change', function () {
if (checkbox.checked) {
console.log('Checked');
var logStream = fs.createWriteStream('C:/Users/solars/Desktop/myFile.txt', { flags: 'a' });
// use {flags: 'a'} to append and {flags: 'w'} to erase and write a new file
logStream.end('\nthis is the end line');
}
But what happens here is:
The first time it is checked, a new line would appear with "this is the end line'"
I would then uncheck, nothing happens as expected
I would recheck and three new lines would appear of "this is the end line'" (all together of 4 lines of "this is the end line')
I would then uncheck, nothing happens as expected
I would recheck and five new lines would appear of "this is the end line'" (all together of 9 lines of "this is the end line')
I believe it is looping somewhere and when it unchecks it adds one to the number of lines to add (if that makes sense)
Any ideas how to fix this?
The whole code:
<div class="main">
<h1> click me </h1>
<label class="switch"><input type="checkbox" id="togBtn">
<div class="slider round"></div>
</label>
<script>
src = "./renderer.js";
const replace = require('replace-in-file');
//const { dialog } = require('electron').remote;
var fs = require('fs');
var array = fs.readFileSync('C:/Users/solars/Desktop/myFile.txt').toString().split("\n");
var checkbox = document.querySelector('input[type="checkbox"]');
var arrayCheck = array.includes("triangle=3")
//checkbox.checked = (arrayCheck) ? true : checkbox.checked
document.getElementById("togBtn").addEventListener("click", () => {
checkbox.addEventListener('change', function () {
if (checkbox.checked) {
console.log('Checked');
var logStream = fs.createWriteStream('C:/Users/solars/Desktop/myFile.txt', { flags: 'a' });
// use {flags: 'a'} to append and {flags: 'w'} to erase and write a new file
logStream.end('\nthis is the end line');
} else {
console.log('Not checked');
//code that set tiangle to equal 3
}
});
});
</script>
</div>
Your current code says "Every time togBtn is clicked, add a listener to checkbox that will be executed when it changes."
Click the button five times - you add five change listeners to checkbox. Every single listener will run when the checkbox changes state, and the more times you click togBtn, the more times the listener function will run in the future.
To fix, you need to structure your code in a way that you can never add more than one listener unless you really, really need to (unlikely). This will certainly involve moving the change listener outside of the click listener.

Categories