I just starting learning the basics of JavaScript and HTML5. I'm trying to create a cookbook app where users can add recipes. These recipes are stored inside of an array and looped through a table with a for loop. The user can also edit the table. One of the main features is supposed to be that when a row of the table is empty, it automatically gets deleted. I tried using display: none with CSS, but it doesn't work. Here is my CSS for the table:
form.addEventListener("submit", function (event) {
let titleRow = table.insertRow();
let stepsRow = table.insertRow();
let titleCell = titleRow.insertCell();
let stepsCell = stepsRow.insertCell();
titleCell.innerHTML = inputTitle.value;
stepsCell.innerHTML = inputSteps.value;
titleCell.contentEditable = true;
stepsCell.contentEditable = true;
inputTitle.value = inputTitle.defaultValue;
inputSteps.value = inputSteps.defaultValue;
}, false);
td: empty {
display: none;
}
tr: empty {
display: none;
}
<form id="form">
<label>Insert Recipe: </label>
<textarea id = "inputTitle" rows="1" cols="50" placeholder="Recipe Title"></textarea>
<textarea id = "inputSteps" rows = "5" cols = "50" placeholder="Recipe Steps"></textarea>
<button type="submit">Add Recipe</button>
</form>
<table id = "table"></table>
The table element refers to an HTML5 table, inputTitle refers to one textarea, and inputSteps refers a different textarea.
I may have mistakes in my JavaScript.
Thanks in advance!
First, since you aren't submitting the data to any other resource, you shouldn't be using a form or a submit button.
Now, the simplest solution is to just perform validation on the inputs before building the table. if the input is empty, don't build those rows.
Lastly, don't use .innerHTML when the string you are working with doesn't contain any HTML. Use .textContent instead because .innerHTML has performance and security considerations.
let inputTitle = document.getElementById("inputTitle");
let inputSteps = document.getElementById("inputSteps");
document.querySelector("button").addEventListener("click", function (event) {
// First, just check to see if there was valid input
if(!inputTitle.value || !inputSteps.value){
alert("You must fill in a title and a recipe");
return; // Exit function
}
let titleRow = table.insertRow();
let stepsRow = table.insertRow();
let titleCell = titleRow.insertCell();
let stepsCell = stepsRow.insertCell();
titleCell.textContent = inputTitle.value;
stepsCell.textContent = inputSteps.value;
titleCell.contentEditable = true;
stepsCell.contentEditable = true;
});
<label>Insert Recipe: </label>
<textarea id = "inputTitle" rows="1" cols="50" placeholder="Recipe Title"></textarea>
<textarea id = "inputSteps" rows = "5" cols = "50" placeholder="Recipe Steps"></textarea>
<button type="button">Add Recipe</button>
<table id = "table"></table>
Related
I basically have an input of type number
<input type="number" id="no_pi" name="" onkeyup="des()">
<div id="extract"></div>
and function
function des() {
var ext = document.getElementById('extract');
var va = Number(document.getElementById('no_pi').value);
for (var i = 0; i = va; i++) {
ext.innerHTML = "<input type='number' name='' class='form-control'><div class='input-group-text'>cm</div>";
}
}
I just want to instantly generate x number of inputs in div based on user input.
When the user input any number, the page just crashes down. I think the page is going in infinite loop, but I think it is not the case.
Any idea how to achieve this
There's several errors :
In your loop : i = va (this is why it crashes)
You erase the content of the div ext each time you iterate, instead of adding content
By listening on keyup event, you add some content on each key hit. Finally if the user submit 12, it will generate 1 + 12 elements. You should pass the value using a form (by doing this you can also add easily the value control in the input element).
As perfectly mentionned by #Andy in the comments, innerHTML += is a very bad idea. You should generate your elements using document.createElement or insertAdjacentHTML.
Some advices :
Use an event listener instead of the onkeyup attribute
Avoid this kind of variable names, be more explicit
Use const and let instead of var
Here's a version which fixes all that issues :
document.getElementById('elementsNumberForm').addEventListener('submit', event => {
event.preventDefault();
const targetElement = document.getElementById('extract');
const inputValue = document.getElementById('no_pi').value;
for (let i = 0; i < inputValue; i++) {
targetElement.insertAdjacentHTML('beforeEnd', '<input type="number" name="" class="form-control" /><div class="input-group-text">cm</div>');
}
});
<form id="elementsNumberForm">
<input type="number" id="no_pi" min="1" />
<input type="submit" />
</form>
<div id="extract"></div>
Your key issue is how you're using your loop. i = va isn't going to accomplish what you want. It should be a check that the index in the iteration is less than the number represented by the value in your input. It should be i < va.
The other issue is that you're not adding to the HTML, just ensuring that the HTML is just one input.
I've adjusted the code in your question to remove the inline JS and use addEventListener instead, and also to use an array to store the HTML built from the loop which can then be applied to the extract element.
// Cache the elements outside of the loop
// and attach a change listener to the noPi element
const extract = document.getElementById('extract');
const noPi = document.getElementById('no_pi');
noPi.addEventListener('change', des, false);
function des() {
const limit = noPi.value;
// Check that we haven't gone into
// negative numbers
if (limit >= 0) {
// Create an array
const html = [];
// Loop, pushing HTML into the array, until
// we've reached the limit set by the value in noPi
for (let i = 0; i < limit; i++) {
html.push('<input type="number" class="form-control"><div class="input-group-text">cm</div>');
}
// `join` up the array, and add the HTML
// string to the extract element
extract.innerHTML = html.join('');
}
}
<input type="number" id="no_pi" />
<div id="extract"></div>
Additional information
join
I see that you want to use an input field to insert the number of inputs to create.
I see a better way to start learning insert the number of inputs with a prompt, and then scale the project.
You can start like this: (hope it make sense to you)
<div style="height: 300px; background-color: #ccc;" class="container"></div>
we have this div that is going to be filled with the inputs
Then we have the script:
const container = document.querySelector('.container');
const runTimes = prompt("How many inputs wnat to create?");
for(let i = 0; i < runTimes; i++){
let newInput = document.createElement('input');
newInput.innerHTML = "<input type='number' name='' class='form-control'>";
container.appendChild(newInput);
}
In the for loop, we create the element input, then with the .innerHTML we add the HTML we want. to end the loop, we need to append the created input element to the div we have.
hope it makes sense to you, :)
when you get the idea with the prompt , I´ve done this project more pro jaja.
<div style="height: 300px; background-color: #ccc;" class="container"></div>
<input type="text" class="numberTimes" onkeyup="getValue()">
we add an event listener to the input with the function getValuue, and the script like this:
const container = document.querySelector('.container');
function getValue(){
let runTimes = document.querySelector('.numberTimes').value;
document.querySelector('.numberTimes').value= "";
for(let i = 0; i < runTimes; i++){
let newInput = document.createElement('input');
newInput.innerHTML = "<input type='number' name='' class='form-control'>";
container.appendChild(newInput);
}
}
This line document.querySelector('.numberTimes').value= ""; is to reset the input field.
So whenever insert a value on the input it creates that number of inputs in the container and cleans the input field :)
EDIT: I have updated the code with the answers.
I have a increment function that is working fine. However:
1. I would like to set some limits based on the total number available in one of the span. For example, 10. So the incrementing can't be more than 10. #DONE
Another issue is that I am planning to have multiple rows and before I save I want to make sure if we count the increments in every row it should not be more than 10 as well. If it decrease the total number (span) dynamically would be nice.
I'm adding rows dynamically with the ADD button, how can I add news rows that actually work with the current functions? Mine rows just clone the first one and the increment function is disabled.
document.addEventListener('DOMContentLoaded', async function() {
document.querySelector('#addlocationdest').addEventListener('click', add);
});
function add() {
var x = 1;
var container = document.getElementById('destination');
var detail = document.getElementById('row');
var clone = detail.cloneNode(true);
clone.id = "destination" + x;
x++;
container.appendChild(clone);
}
window.addEventListener("load", () => {
let elTotalQuantity = document.querySelector("#totalqty");
let totalQuantity = parseInt(elTotalQuantity.innerHTML);
function getSumOfRows() {
let sum = 0;
for (let input of document.querySelectorAll("form .row > input.quantity"))
sum += parseInt(input.value);
return sum;
}
for (let row of document.querySelectorAll("form .row")) {
let input = row.querySelector("input");
row.querySelector(".increment").addEventListener("click", () => {
if (getSumOfRows() >= totalQuantity) return;
input.value++;
elTotalQuantity.innerHTML = totalQuantity - getSumOfRows();
});
row.querySelector(".decrement").addEventListener("click", () => {
if (input.value <= 0) return;
input.value--;
elTotalQuantity.innerHTML = totalQuantity - getSumOfRows();
});
}
});
<div id="location" class="hide">
<div class="title">Transfer details</div><br>
<div class="line padded-s">Total Quantity: <span>10</span></div>
<br>
<form>
<label>New Total Quantity at this location: <span id="totalqty">10</span></label>
<br>
<div id="destination">
<div id="row" class="row">
<button type="button" class="decrement">-</button>
<input type="text" class="quantity" value="0" readonly/>
<button type="button" class="increment">+</button>
<a>Location: </a>
<input type="text" class="location" value="0" readonly/>
</div>
</div>
</form>
<label>Total being transfer: <p id="total-sum"></p></label>
<br>
<button type="button" id="addlocationdest">ADD</button>
<button type="button" id="removelocationdest">REMOVE</button>
</div>
Prologue
As long as the total quantity is fixed at the beginning of the script-execution, this works. Otherwise, it would be best to save the actual allowed total quantity as an attribute, and observe it using a MutationObserver. That way you can update your max. value in your code dynamically, when the total quantity-attribute changes. You can define custom attributes by naming them "data-*" where "*" is a custom name.
Solution for your problem
You are using the same ID on multiple elements. What you meant were classes, so change id="increment" to class="increment", and the same for decrement.
Since we don't want to input something with the buttons, but add listener to them, I'd say it is better to actually use <button>. In forms, buttons act as type="submit", which we don't want, so we need to change it to type="button".
Since the rows and the total quantity actually belong together, it is wiser to place them together into one <form>-element. However, you can still group the buttons and inputs as a row together using <div>.
Now regarding the in-/decrementing of the row's values and the total quantity:
Save the allowed total quantity in a variable
Add event-listener to the corresponding buttons
If action is valid, change row's value
Update total quantity number to totalQuantity - getSumOfRows()
To add new rows dynamically, we create and setup such an element, and append it to the form. See the appendNewRow()-function below.
Sidenote
I have added the readonly attribute to the input-fields so that you cannot enter numbers via keyboard.
window.addEventListener("load", () => {
let elTotalQuantity = document.querySelector("#totalqty");
let totalQuantity = parseInt(elTotalQuantity.innerHTML);
function getSumOfRows() {
let sum = 0;
for (let input of document.querySelectorAll("form .row > input.quantity"))
sum += parseInt(input.value);
return sum;
}
function updateTotalQuantity() {
elTotalQuantity.innerHTML = totalQuantity - getSumOfRows();
}
function appendNewRow() {
let row = document.createElement("div");
row.classList.add("row");
let child;
// input.quantity
let input = document.createElement("input");
input.classList.add("quantity");
input.value = "0";
input.setAttribute("readonly", "");
input.setAttribute("type", "text");
row.append(input);
// button.increment
child = document.createElement("button");
child.classList.add("increment");
child.innerHTML = "+";
child.setAttribute("type", "button");
child.addEventListener("click", () => {
if (getSumOfRows() >= totalQuantity) return;
input.value++;
updateTotalQuantity();
});
row.append(child);
// button.increment
child = document.createElement("button");
child.classList.add("decrement");
child.innerHTML = "-";
child.setAttribute("type", "button");
child.addEventListener("click", () => {
if (input.value <= 0) return;
input.value--;
updateTotalQuantity();
});
row.append(child);
// button.remove-row
child = document.createElement("button");
child.classList.add("remove-row");
child.innerHTML = "Remove";
child.setAttribute("type", "button");
child.addEventListener("click", () => {
row.remove();
updateTotalQuantity();
});
row.append(child);
document.querySelector("form .rows").append(row);
}
document.querySelector("form .add-row").addEventListener("click", () => appendNewRow());
appendNewRow();
});
<form>
<label>Total Quantity: <span id="totalqty">10</span></label>
<br>
<div class="rows">
</div>
<button type="button" class="add-row">Add new row</button>
</form>
QuerySelector only selects the first occurrence so you haven't really added a listener to the second "row". You should use querySelectorAll but, instead of unique ids, use classes.
<input class="increment" type="button" value="+" />
Now you can use document.querySelectorAll(".increment") to get all elements in an array.
You can traverse in the DOM by using parentElement. By knowing which button you clicked, you can traverse up to the form element and then select the first child - which is an input. A more dynamic way would be to use querySelector to select the input, in case the HTML change in the future. Anyway, that's how you can know which input to manipulate based on where the buttons are in the DOM.
I added two global variables, totalSum and maxSum. maxSum is fetched from your span element (which I assigned an unique id to). totalSum makes sure that all inputs combined doesn't exceed maxSum.
You had some duplicate code, so I refactored it into a new method: changeValue.
In all, I think the code speaks for itself.
Oh, this code doesn't take into account that the user can change the value inside the input. I will leave that for you to figure out with an "oninput" listener on each text input.
var totalSum = 0; // 3
var maxSum = 0
var totalSumElement = null;
document.addEventListener('DOMContentLoaded', async function() {
totalSumElement = document.getElementById('total-sum');
maxSum = document.getElementById('max-sum').innerText;
var incrementElements = document.querySelectorAll('.increment'); // 1
var decrementElements = document.querySelectorAll('.decrement');
addListener('click', incrementElements, incrementValue);
addListener('click', decrementElements, decrementValue);
});
function addListener(type, elementArr, func) {
for (element of elementArr) {
element.addEventListener(type, func);
}
}
function withinRange(newValue) {
var maxReached = newValue > maxSum; // 3
var zeroReached = newValue < 0;
return !maxReached && !zeroReached;
}
function changeValue(event, change) { // 4
if (withinRange(totalSum + change)) {
let parent = event.currentTarget.parentElement; // 2
let input = parent.children[0];
let value = parseInt(input.value) || 0;
if (withinRange(value + change)) {
input.value = value + change;
totalSum = totalSum + change;
}
}
totalSumElement.textContent = `Total: ${totalSum}`;
}
function incrementValue(event) {
changeValue(event, 1);
}
function decrementValue(event) {
changeValue(event, -1);
}
#totalqty {
padding-bottom: 1rem;
}
<div id="totalqty" class="line padded-s">Total Quantity: <span id="max-sum">10</span></div>
<form>
<input type="text" value="0" />
<input class="increment" type="button" value="+" />
<input class="decrement" type="button" value="-" />
</form>
<form>
<input type="text" value="0" />
<input class="increment" type="button" value="+" />
<input class="decrement" type="button" value="-" />
</form>
<p id="total-sum"></p>
Hey I have the following problem, I want to display two text inputs as a list element but I do not know how to append various variables. The code below only shows the first input in the list.
function addLi () {
let x = document.createElement("LI");
let name= document.createTextNode(document.getElementById("name").value);
let city= document.createTextNode(document.getElementById("city").value);
x.appendChild(name)
document.getElementById("list").appendChild(x);
return false;
}
You could do it this way (just an example with a loop):
document.getElementById('add-btn').addEventListener('click', addLi);
function addLi() {
const items = ['name', 'city'];
for (let item of items) {
const li = document.createElement('li');
const value = document.createTextNode(document.getElementById(item).value);
li.appendChild(value);
document.getElementById("list").appendChild(li);
}
}
<input id="name" value="John Doe"> <input id="city" value="Sydney">
<button id="add-btn">Add to list</button>
<ul id="list"></ul>
I got something simple but I'm obviously too stupid.
I have two inputs in my HTML. 1st is company and 2nd is shares. I'm creating UL with the companies. The thing is I want to do that when I enter a company name that already exist in the array I want to update the shares of that company only. I will share the code I made so far.
<body>
<form>
<input id="company" placeholder="Company" type="text" />
<input id="input" placeholder="Shares" type="number" />
<button id="btn">Add</button>
</form>
<ul id="content"></ul>
</body>
And the JavaScript code is here:
const button = document.getElementById("btn");
const content = document.getElementById("content");
let companiesArray = [];
function myCompany(e) {
e.preventDefault();
const inputShares = document.getElementById("input").value;
const inputCompany = document.getElementById("company").value;
let obj = {
company: inputCompany,
shares: inputShares,
};
for (const item of companiesArray) {
if (item.company === obj.company) {
//TO DO
console.log("Match");
return;
}
}
const li = document.createElement("li");
content.appendChild(li).textContent = `${obj.company} - ${obj.shares}`;
companiesArray = [...companiesArray, obj];
console.log(companiesArray);
}
button.addEventListener("click", myCompany);
So again, when I enter a unique company => li is created in the ul. If I enter the same company I just want to update the shares without adding new li.
if (item.company === obj.company) {
//TO DO
const list = document.getElementsByTagName('li')
const element = [...list].filter((item) => item.innerText.split(" ")[0] === obj.company)[0]
element.innerText = `${obj.company} - ${obj.shares}`
console.log("Match");
return;
}
Hope this will help :)
I found this code on here (thanks to Xavi López) and it is ideal for what I need to add to my project but I'm in need of some help adding a Form post and submit button in JavaScript. I have no knowledge on this subject and I've tried looking at some example but non of them seem to work. I would be grateful if someone could help me. After the user adds the relevant number of input boxes and adds there data, I would like to have a submit button which will POST the results to another web page (result page)
I have added the solution to the below coding (thank you MTCoster) but I'm now try to find a solution to having the submit button appear only when an entry has been added. I have tried different methods but non will work.
function addFields() {
// Number of inputs to create
var number = document.getElementById('member').value;
// Container <div> where dynamic content will be placed
var container = document.getElementById('container');
// Clear previous contents of the container
while (container.hasChildNodes()) {
container.removeChild(container.lastChild);
}
for (i = 0; i < number; i++) {
// Append a node with a random text
container.appendChild(document.createTextNode('Member ' + (i + 1) + ' '));
// Create an <input> element, set its type and name attributes
var input = document.createElement('input');
input.type = 'text';
input.name = 'member' + i;
container.appendChild(input);
// Append a line break
container.appendChild(document.createElement('br'));
}
}
<input type="text" id="member" name="member" value="">Number of Pins: (max. 48)<br>
Add Pinout Entries
<form action="result.asp" method="POST">
<div id="container"></div>
<input type="submit" value="Add Data">
</form>
You’re almost there - all you need to do is wrap your inputs in a <form> element:
function addFields() {
// Number of inputs to create
var number = document.getElementById('member').value;
// Container <div> where dynamic content will be placed
var container = document.getElementById('container');
// Clear previous contents of the container
while (container.hasChildNodes()) {
container.removeChild(container.lastChild);
}
for (i = 0; i < number; i++) {
// Append a node with a random text
container.appendChild(document.createTextNode('Member ' + (i + 1) + ' '));
// Create an <input> element, set its type and name attributes
var input = document.createElement('input');
input.type = 'text';
input.name = 'member' + i;
container.appendChild(input);
// Append a line break
container.appendChild(document.createElement('br'));
}
}
<input type="text" id="member" name="member" value="">Number of Pins: (max. 48)<br>
Add Pinout Entries
<form action="/url/to/post/to" method="POST">
<div id="container"></div>
<input type="submit">
</form>
If you’d like the submit button to only appear after at least one input is visible, you could add it at to div#container at the end of addFields(). I’ll leave this as an exercise to the OP, since it’s not much different to how you’re adding the input fields.