Add and Remove Buttons JavaScript - javascript

I've tried this code that was posted but I can't get it to work the way I need. I can get it to create both fields but I can't make them look the same as the format of the first fields. How can I Add the entire section and make it look the same? and control the maximum number of activities inserted.
TIA
This is what I'm trying to acomplish
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn{color:red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>

First, the problems:
I can get it to create both fields but I can't make them look the same as the format of the first fields...
This is likely because in your original .activityGroup there are a number of <br> elements between the <input> and the <textarea> elements – incorrectly – used for spacing which you haven't inserted into the copy.
Also, two of the attributes of the original <textarea> (rows="5", and cols="50") are omitted from the one you create within the function, not to mention the line-breaks and other white-space.
This can be rectified by simply creating, and appending, those other elements:
// first, some utility functions to simplify life:
const D = document,
get = (sel, ctx = D) => ctx.querySelector(sel),
getAll = (sel, ctx = D) => [...ctx.querySelectorAll(sel)],
create = (tag, props) => Object.assign(D.createElement(tag), props),
// renamed variables for consistency between JavaScript
// and the HTML upon which it acts:
addBtn = get("#addBtn"),
activity = get("#activity"),
// creating a generator function to use as a counter;
// fundamentally this is little different from the
// original i = 0 and then incrementing in the function
// body, but the generator can't be affected by an
// accidental decrement:
iterator = function*() {
// initial value:
let count = 1;
// a deliberate infinite loop, but the
// generator function exits/pauses at
// every yield so while this is an 'infinite
// function' it doesn't cause any blocking:
while (true) {
// yields value and then increments it:
yield count++
}
},
// getting a reference to the generator function:
counter = iterator(),
remove = (e) => e.currentTarget.closest('div').remove();
addBtn.addEventListener("click", function() {
// getting the current count of all elements matching
// the selector within the document:
const currentCount = getAll('.activityGroup').length,
// retrieving the next value from the
// generator function:
i = counter.next().value;
// if the currentCount is greater than 4 (again, using
// a Yoda condition):
if (4 < currentCount) {
// exit the function:
return false;
}
// creating a new <div>:
const activityGroup = create('div', {
// assigning the class-name of 'activityGroup':
className: 'activityGroup'
}),
// creating a new <button>:
removeButton = create('button', {
// assigning the class-name of 'delBtn',
className: 'delBtn',
// and the text-content of 'X':
textContent: 'X'
}),
input = create('input', {
type: 'text',
id: `activity_${i}`,
placeholder: `Activity ${i}`
}),
textarea = create('textarea', {
id: `activity_description_${i}`,
placeholder: `Activity description ${i}`
});
// binding the remove() function as the'click' event-handler
// on the removeButton:
removeButton.addEventListener('click', remove);
// using the HTMLElement.dataset API to set the
// custom data-count attribute to the current
// value of the i variable (for use in the CSS):
activityGroup.dataset.count = i;
// using Element.append() to append multiple elements
// in one call:
activityGroup.append(input, textarea, removeButton);
activity.appendChild(activityGroup);
});
/* caching common values (useful for theming and
maintenance): */
:root {
--bg-primary: hsl(0 0% 0% / 0.3);
--columns: 2;
--fs: 16px;
--spacing: 0.5em;
}
/* a simple CSS reset to remove default
margins and padding, and to have all
elements sized in a way that includes
their padding and border-widths in the
assigned size: */
*,::before,::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-size: var(--fs);
}
#activity {
/* using CSS Grid for layout: */
display: grid;
/* setting a space - held in the custom
property - between adjacent elements: */
gap: var(--spacing);
/* creating a number of columns (equal to the value held
in the --columns property) of equal width, each of one
fraction of the remaining space) */
grid-template-columns: repeat(var(--columns), 1fr);
padding: var(--spacing);
}
.activityGroup {
/* aligning an element in the grid to the start of that
grid (alignment is on the cross-axis of the inline-
axis, the inline-axis being the writing direction
of the language of the user's browser): */
align-self: start;
border: 0.2em solid var(--bg-primary);
display: grid;
gap: inherit;
padding: var(--spacing);
}
.activityGroup::before {
background-color: var(--bg-primary);
/* setting the content of the ::before pseudo element to the
string of 'Activity ' concatenated with the attribute-value
of the data-count attribute: */
content: "Activity " attr(data-count);
display: block;
/* spanning all three grid-columns: */
grid-column: span 3;
/* applying padding to the start of the inline axis: */
padding-inline-start: var(--spacing);
/* moving the element outwards with negative margins,
to place the content against the outer edges of the
element, despite the spacing on that element: */
margin-block-start: calc(-1 * var(--spacing));
margin-inline: calc(-1 * var(--spacing));
}
input,
textarea {
grid-column: span 3;
padding: 0.5em;
}
textarea {
min-block-size: 5em;
resize: vertical;
}
.delBtn {
color: red;
grid-column: 3;
}
/* using a simple media query to modify the values of the CSS Custom
properties as required to improve the experience on different
devices: */
#media screen and (max-width: 700px) {
:root {
--columns: 1;
--fs: 18px;
}
}
<!-- we don't appear to be using jQuery, so I removed that <script> element -->
<!-- switching id and classes to use camelCase rather than
having arbitrary mixed-case format; choose a style you
prefer, but remember that consistency matters more than
the choice you make: -->
<div id="activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<!-- removed the <br> elements, as well as the "rows" and "cols"
attributes from the <textarea>: -->
<textarea placeHolder="Type your description"></textarea>
<button class="delBtn">X</button>
</div>
<!-- Remove button -->
</div>
<!-- changed the id from "add" to "addBtn," purely for consistency
but, again, make your own choice on that: -->
<button id="addBtn">Add Activity</button>
As to your question:
How can I [add an] entire section and...control the maximum number of activities inserted[?]
Controlling the number of total activities/sections on the page is relatively easy and simply requires you to check the number of existing sections before adding a new one:
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
// using document.querySelectorAll() to retrieve a NodeList of all
// elements matching the supplied CSS selector, and then retrieving
// the length of that NodeList:
let currentCount = document.querySelectorAll('.activityGroup').length;
// using a "Yoda condition" to see if the currentCount is greater than
// 4 (this approach guards against the most likely error of an assessment,
// that of accidentally assigning a value instead of comparing); if it is:
if (4 < currentCount) {
// we exit the function here:
return false;
}
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
// creating a <br> element:
const br = document.createElement('br');
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
// appending a clone of the <br> element:
newspan.appendChild(br.cloneNode());
// appending the <br> element:
newspan.appendChild(br);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
// setting the rows and cols attributes:
txtarea.setAttribute('rows', 5);
txtarea.setAttribute('cols', 50);
// appending white-space (approximating what's in the HTML):
newspan.append(document.createTextNode('\n '));
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>
Now, I'd like to improve the above functions as follows with explanatory comments in the code itself:
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
// using document.querySelectorAll() to retrieve a NodeList of all
// elements matching the supplied CSS selector, and then retrieving
// the length of that NodeList:
let currentCount = document.querySelectorAll('.activityGroup').length;
// using a "Yoda condition" to see if the currentCount is greater than
// 4 (this approach guards against the most likely error of an assessment,
// that of accidentally assigning a value instead of comparing); if it is:
if (4 < currentCount) {
// we exit the function here:
return false;
}
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
// creating a <br> element:
const br = document.createElement('br');
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
// appending a clone of the <br> element:
newspan.appendChild(br.cloneNode());
// appending the <br> element:
newspan.appendChild(br);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
// setting the rows and cols attributes:
txtarea.setAttribute('rows', 5);
txtarea.setAttribute('cols', 50);
// appending white-space (approximating what's in the HTML):
newspan.append(document.createTextNode('\n '));
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>
JS Fiddle demo.
References:
CSS:
align-self.
box-sizing.
content.
CSS custom properties.
CSS logical properties.
display.
gap.
grid-column.
grid-template-columns.
grid-template-rows.
inline-size.
margin.
margin-block.
margin-inline.
min-block-size.
padding.
padding-block.
padding-inline.
repeat().
var().
JavaScript:
document.createElement().
document.querySelector().
document.querySelectorAll().
Element.append().
Element.querySelector().
Element.querySelectorAll().
Element.remove().
Event.target.
Event.currentTarget.
EventTarget.addEventListener().
Generator functions.
HTMLElement.dataset.
Node.appendChild().
Object.assign().
yield.

Do you mean "If I have Activity 1, 2, 3, 4, 5 and Delete 3 I want to be able to get Activities 1, 2, 4, 5?"
From what I've gathered, you want a button that adds and deletes activities but you only want it to work when previous activity isn't empty.
In the javascript, when you handle the buttons click event check previous activity, if it's empty, return, if it isn't empty, execute the add/delete code.

Related

How do I edit javascript variables as given by user input?

I want to display an array of numbers, after that if he users wishes to change any of the numbers in array then he/she should write the desired value in its place and click submit button the array gets updated and display the updated array.
I have tried this till now, and gone through numerous articles to find a solution but i couldn't find one which fits here.
This is what my code looks like, it is a bit long but I dont know how much code is adequate so this is it.. please help me get this.
making grid editable
<style>
#container{
display:grid;
grid-template-columns: repeat(9, 8%);
margin-left: 35%;
margin-right:25%;
margin-top:10%;
}
</style>
<body style="background-color:grey">
<div id='container'>
</div>
<input id="someInput" type="text" value=<p id="output"></p>>
<input type="button" value="submit" onClick="doStuff()">
</body>
<script>
var myContainer = document.getElementById('container')
grid=[0,1,4,6,3,0,9,3,2];
for(var i=0;i<9;i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id= `${i}`
var num=grid[i];
myInput.value=num;
myContainer.appendChild(myInput)
}
var x = document.createElement("BUTTON");
function doStuff(){
var nameElement = document.getElementById("someInput");
a = nameElement.value
b = nameElement.id
new_grid=[];
for(var j=0;j<9;j++){
if(j==b){
new_grid[j]=a;
}
else{
new_grid[j]=grid[j];
}
}
for(var i=0;i<9;i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id= `${i}`
var num=new_grid[i];
myInput.value=num;
myContainer.appendChild(myInput)
}
}
window.onload = function() {
//when the document is finished loading, replace everything
//between the <a ...> </a> tags with the value of splitText
document.getElementById("output").innerHTML=grid[4];
}
</script>
'''
then I tried to add a input box so that i can show the array numbers in value attribute of it and when users updates it and clicks submit button it gets updated but then i wasn't able to get array numbers in value attribute of input tag.
This will update your grid array values with the user inputted values on button click.
var myContainer = document.getElementById('container');
grid = [0,1,4,6,3,0,9,3,2];
for(var i=0; i < grid.length; i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id = `${i}`
myInput.value = grid[i];
myContainer.appendChild(myInput);
}
var x = document.createElement("BUTTON");
x.textContent = "UPDATE";
myContainer.appendChild(x);
x.addEventListener("click", UpdateGridFromUserInput, false);
function UpdateGridFromUserInput() {
var allinputs = document.getElementById('container').getElementsByTagName('input');
for(var j = 0; j < allinputs.length; j++) {
grid[j] = allinputs[j].value;
}
console.log(grid);
}
<div id="container">
</div>
Here's an example. I just use input number fields. You can set the min max and add an onChange event if you want to change the value of the nums array.
const nums = [1, 2, 3, 4, 5];
const container = document.querySelector(".container");
nums.forEach(num => {
const numElm = document.createElement("input");
numElm.classList.add("number");
numElm.type = "number";
numElm.min = 1;
numElm.max = 9;
numElm.value = num;
container.appendChild(numElm);
});
.container {
display: flex;
justify-content: space-between;
}
.number {
padding: .5rem;
width: 2rem;
text-align: center;
border: 2px solid grey;
border-radius: 10px;
}
<div>
<p>Selected numbers</p>
<div class="container" />
</div>
I think you have got relevant solutions already, but I would like to show you "little another way" which save a few rows of code and it is more optimal with larger arrays.
Instead of the iterating array when you want to change a single number, you can add an event listener to it. You save an ID to the element by the index of the array, so it is easy to approach the actual position in the array.
First thing first, I would like to comment on a few things in your code, which could be better (we all learn!)
for(var i=0;i<9;i++){var myInput = document.createElement('input') ...
You want to create an input for each element in the array. As a programmer, you know that there will be 9 elements, but (as a programmer :)) you don't always want to remember and count how many elements the array has. It is better to use grid.length which returns the actual length of the array.
function doStuff(){...}
Always name your functions as what they are really doing. It really helps!
var nameElement = document.getElementById("someInput");
a = nameElement.value
b = nameElement.id
Always name your properties as what they actually mean. You could use "input", "inputValue", "inputId". Helps too :)
In doStuff() function, you are creating other inputs again. I think it is not really necessary when you have already created them.
Second thing second let's look at how it looks like as I described before.
Here is the code (use and learn ES6, it's better, trust me):
let array = [0, 1, 4, 6, 3, 0, 9, 3, 2];
let container = document.getElementById("container");
let output = document.getElementById("arrayResult");
output.innerHTML = array;
//create inputs for the array
for (let i = 0; i < array.length; i++) {
let inputElement = document.createElement("input");
inputElement.type = "number";
inputElement.id = i;
inputElement.value = array[I];
//if an user change the value, update the array at its position
inputElement.addEventListener("input", () => {
let id = inputElement.id;
array[id] = inputElement.value;
})
container.appendChild(inputElement);
}
//create submit button
let submit = document.createElement("input");
submit.type = "submit";
submit.value = "Change";
container.appendChild(submit);
//update output
submit.addEventListener("click", () => {
output.innerHTML = array;
});
<div id="container">
</div>
<div>
<label>Result:</label>
<label id="arrayResult"></label>
</div>

How to make a clone of a square in Javascript

I have an exercise for a web development school, which tells me : "how to clone a square 7 times, and augmenting his number by 1 ( from 1 to 8 then).
I think I need a "while" cycle, with incrementation until i = 8, but I'm not sure. Also, can I create a variable with an html element inside, and then try to duplicate it?
// create the first square and insert it into doc
const square = document.createElement('div');
square.style.width = '50px';
square.style.height = '50px';
square.style.margin = '10px';
square.style.backgroundColor = 'blue';
square.innerText = 1;
document.body.appendChild(square);
// simple cycle where i begins from needed number
for (let i = 2; i < 9; i++) {
const clone = square.cloneNode();
clone.innerText = i;
document.body.appendChild(clone);
}
Hopefully this will make clear how to select elements on the page, use a loop to make more, and change what is inside each element.
// Identifies existing HTML elements so JavaScript can interact with them
const container = document.getElementById("container");
const original = document.querySelector(".square");
// Identifies a constant and a variable for our loop
const total = 8;
let number = 1;
// Runs a loop as long as the condition is true
// (Note: It won't run once number == 8, but the time before that will make square #8)
while(number < total){
// Changes a value that will effect whether the condition is true
number++;
// Prints some information about what this iteration of the loop is doing
console.log("making square number " + number);
// Makes a copy of an HTML element
const copy = original.cloneNode();
// Changes the copy to be different from the original
copy.innerHTML = number;
// Adds the copy to the page by appending it to the `container` element
container.appendChild(copy);
}
.square{
height: 16px; /* How tall the element will be, not including padding */
width: 16px;
padding: 16px; /* How much extra (blue) space the element will include */
margin: 16px; /* How much extra (white) space the element will have around it */
font-size: 16px;
text-align: center
}
.blue{
background-color: lightskyblue;
}
<div id="container">
<div class="blue square">1</div>
</div>

iterating across multiple buttons using key value pairs array to add event listeners with loops

I am working on a simple calculator project
I am attempting to automate adding event listeners to the various numeric buttons (1-9).
The event listeners will listen to click events on the buttons leading to a change in the display section of the HTML (class = .display)
key value pairs being b1-b9 containing the various corresponding values.
I have come up with the below FOR EACH loop. For some reason it causes all numerical buttons to apply the number 9; which i believe is the cause of the for each loop.
I am unsure how to quite fix it. I have also come up with an alternative FOR loop that leads to another problem. pairs[Properties[i]].toString() returns undefined.
interestingly if i swap pairs[Properties[i]].toString() out to just i then the SAME issue occurs
Help really appreciated and thank you..
const pairs = {
b1: 1,
b2: 2,
b3: 3,
b4: 4,
b5: 5,
b6: 6,
b7: 7,
b8: 8,
b9: 9,
};
var Properties = Object.keys(pairs);
function loadButtons () {
for (var item in pairs) {
//for each key property in pairs
console.log(item);
let targetCell = document.querySelector("." + item.toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
newDisplay = currentDisplay + pairs[item].toString();
document.querySelector(".display").innerHTML = newDisplay;
})
// console.log(pairs[item]);
// // pairs[item] retrieves the value to that "key"
}
};
function alternative() {
var i;
var Properties = Object.keys(pairs);
for (i = 0; i < Properties.length; i++) {
let targetCell = document.querySelector("." + Properties[i].toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
newDisplay = currentDisplay + pairs[Properties[i]].toString();
document.querySelector(".display").innerHTML = newDisplay;
})
};
};
Expected should be clicking of 1 to add a string "1" to the current string of the calculator, so on .
function onClick(item, pairs) {
return () => {
// you want it to manipulate the display as and when clicked
var currentDisplay = document.querySelector(".display").innerHTML.toString();
var newDisplay = currentDisplay + pairs[item].toString();
document.querySelector(".display").innerHTML = newDisplay;
}
}
var Properties = Object.keys(pairs);
function loadButtons () {
for (var item in pairs) {
//for each key property in pairs
console.log(item);
let targetCell = document.querySelector("." + item.toString())
// querySelector only targets the FIRST element found
// in this case only 1 for that name
console.log(targetCell);
targetCell.addEventListener('click', onClick(item, pairs))
// console.log(pairs[item]);
// // pairs[item] retrieves the value to that "key"
}
};
You should use event delegation instead of looping over and attaching events to every button. Here's an example:
var keyboard = document.getElementById('keyboard');
var display = document.getElementById('display');
keyboard.addEventListener('click', function(ev) {
var val = ev.target.id;
if (ev.target.localName === 'button') {
display.innerText += val;
}
});
.calculator {
width: 300px;
background: whitesmoke;
}
#display {
height: 50px;
background: #d2d2d2;
border: 1px solid #9c9c9c;
margin: 10px auto 10px;
font-size: 20px;
line-height: 50px;
padding: 0 10px;
}
#keyboard {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
button {
font-size: 20px;
padding: 20px;
margin: 5px;
cursor: pointer;
}
<div class="calculator">
<div id="display" contenteditable="true" >
</div>
<div id="keyboard">
<button id="0">0</button>
<button id="1">1</button>
<button id="2">2</button>
<button id="3">3</button>
<button id="4">4</button>
<button id="5">5</button>
<button id="6">6</button>
<button id="7">7</button>
<button id="8">8</button>
<button id="9">9</button>
</div>
</div>
i will do it this way but that not the only one i guess and there could be better ways.
const BUTTONS_NAMESVALUES={
//-- sound awful when a loop can do that!
bt0:0,bt1:1,bt2:2,bt3:3,bt4:4,bt5:5,bt6:6,bt7:7,bt8:8,bt9:9
}
function checkButtonValue(bt){
if(BUTTONS_NAMESVALUES[bt.id] !=null && BUTTONS_NAMESVALUES[bt.id] !='undefined'){
return bt.innerHTML;
}return;
}
//a button may look like that
<button id="bt1">1</button>
//-- with listener:
document.getElementById('bt1').addEventListener('click', function(e){
let chk=checkButtonValue(this);
if(chk!=null && chk!='undefined' && chk!=''){
document.getElementById('calculatorScreen').innerHTML=''+document.getElementById('calculatorScreen').innerHTML+chk;
}
});
I hope that help. I just replace the class name '.display' who can easily be a source of error(because it's the name of a CSS property and anything is display in HTML+ using an id better in that case because it's a unique element and can't be mistaken, classes aren't) and is not very accurate(as i write a correct constante name who has some meaning instead of pairs who means really nothing ^^).
Neither i 've automated the code into a loop but that's the easy part who is ever in your script.

Why won't JavaScript function create an HTML element?

I have a method, which is meant to create divs within another div... However it won't work...
Here is the method:
populateSquares(){
let relatedTagToPopulation = document.getElementById("questionp");
let whatTextIs = relatedTagToPopulation.textContent;
for (let u=0;u<this.stemQuestions.length;u++){
if (this.stemQuestions[u]==whatTextIs){
var populateSquaresPertinentInt = u;
}
}
for (let i=0;i<this.stemAnswers.length;i++){
if (i==populateSquaresPertinentInt){
let numberOfSquaresToAdd = this.stemAnswers[i].length;
for (let j=0;j<numberOfSquaresToAdd;j++){
let elToAdd = document.createElement("<div id='ans"+j+"' class='lans'></div>");
let elToAddInto = document.getElementById("answeri");
elToAddInto.appendChild(elToAdd);
}
}
}
}
It gives out this error...
Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('<div id='ans0' class='lans'></div>') is not a valid name.
If you are using JavaScript, you should follow the document: https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement, and here: CreateElement with id?
let elToAdd = document.createElement('div')
// then call `elToAdd.xxxx` to add attributes or do other operation on the element
elToAdd.setAttribute("id", "ans" + i);
// ... more
If you are using jQuery, you can use:
let elToAdd = jQuery("<div id='ans"+j+"' class='lans'></div>")
Three Ways to Create Tags
The following examples all do the same thing:✱
Create an <article> tag with the class: .post and add it to the <main id="main"> tag.
✱ There's an exception see #2 .innerHTML
document.createElement(tagName)
The only parameter is a tagName (ex. "DIV", "SPAN", "IFRAME", etc.). Once created it needs to be added to the DOM:
const post = document.createElement("ARTICLE");
post.className = "post";
document.getElementById('main').appendChild(post);
This is an old yet stable method but it takes two lines to create one barebones tag. More code is necessary to assign attributes and content.
.innerHTML += htmlString
This property will parse a tag(s) out of a given String within the targeted tag. If an = operator is used all content of the targeted tag is overwritten with the htmlString. If += operator is used the htmlString will be appended to the content within the targeted tag.
document.querySelector('main').innerHTML += `<article class='post'></article>`;
This pattern is simple and versatile. In one line multiple tags can be created with attributes and content. It's limited to either overwriting content: = or appending to content: +=.
✱Edit: Kaiido has informed me that .innerHTML will replace everything so if you are concerned about event bindings or references don't use it. See comments below.
.insertAdjacentHTML(position, htmlString)
This is .innerHTML on steroids. It will insert before/after/inside/outside a given htmlString of a targeted tag. The first parameter is one of four strings that determine the position of insertion relative to the targeted tag:
"beforebegin" <div id="target"> "afterbegin" text content "beforeend" </div> "afterend"
The second parameter is the htmlSting to be inserted.
document.getElementsByTagName('MAIN')[0].insertAdjacentHTML('afterbegin', `
<article class='post'></article>
`);
I couldn't follow your code but it's supposed to be a method? So the demo has an object called populate and there's a factory function called documentSection() that creates objects and inherits the method .createSection() from populate.
Demo
let text = ['post 1', 'post 2', 'post 3'];
let archives = ['document 1', 'document 2', 'document 3'];
const populate = content => ({
createSections: () => {
let idx = 0;
const target = document.querySelector("main");
/*
Pattern 1 - document.createElement(tagName)
*/
const section = document.createElement('SECTION');
section.id = content.title;
target.appendChild(section);
/*
Pattern 2 - .innerHTML += htmlString
*/
section.innerHTML += `<h2>${content.title}</h2>`;
for (let text of content.text) {
idx++;
/*
Pattern 3 - .insertAdjacentHTML(position, htmlString)
*/
section.insertAdjacentHTML('beforeend', `<article id="${content.title}-${idx}" class="t">${text}</article>`);
}
}
});
const documentSection = (title, text) => {
let content = {
title: title,
text: text
};
return Object.assign(content, populate(content));
};
const post = documentSection('Post', text);
const archive = documentSection('Archive', archives);
post.createSections();
archive.createSections();
main {
display: table;
border: 3px ridge grey;
padding: 2px 10px 10px;
}
h1 {
font: 900 small-caps 1.5rem/1 Tahoma;
margin-bottom: 8px
}
h2 {
font: 700 small-caps 1.2rem/1 Tahoma;
margin-bottom: 8px
}
section {
border: 2px solid #000;
padding: 2px 8px 8px;
}
article {
font: 400 1rem/1.25 Arial;
}
.t::before {
content: attr(id)': ';
font-weight: 700;
}
<main>
<h1>Documents</h1>
</main>

How to know that which button is clicked, if buttons are added dynamically using javascript?

I don't know how to solve that problem, If anyone knows how to solve that then please let me know. If required, then I will send the code personally to figure out the mistakes.
There are two containers: left and right. In Left, there is a from where I take values of title, description and status(active/inactive) from text boxes and radio buttons(active/inactive). Then after pressing submit button all values are filled in table of right-container with edit and delete button attached after clicking submit button every time. I want to delete specific row where delete button is clicked. But I don't know how to access that button while onclick function(doDelete()) is same in all the buttons.
function fillData() {
var table = document.getElementById("myTable");
var counter = table.querySelectorAll('tr').length;
var key = counter;
var row = table.insertRow(counter);
row.id = "row-" + key;
var titleCell = row.insertCell(0);
var descCell = row.insertCell(1);
var statusCell = row.insertCell(2);
var actionCell = row.insertCell(3);
var editButton = document.createElement("button");
editButton.innerText = "Edit";
editButton.id = "edit-" + key;
editButton.setAttribute("onclick", "doEdit()");
var delButton = document.createElement("button");
delButton.innerText = "Delete";
delButton.id = "delete-" + key;
delButton.setAttribute("onclick", "doDelete()");
titleCell.innerHTML = document.getElementById("panel-title").value;
descCell.innerHTML = document.getElementById("panel-description").value;
statusCell.innerHTML = (function () {
var radios = document.getElementsByName("status");
for (i = 0, len = radios.length; i < len; i++) {
if (radios[i].checked) {
return radios[i].value;
}
}
}());
actionCell.appendChild(editButton);
actionCell.appendChild(delButton);
var delBtnArr = document.querySelectorAll('input[type="button"]');
console.log(delBtnArr);
}
Actual Results: After pressing delete button, the whole rows are deleted.
Expected Results: After pressing delete button, the specific row is deleted where button is clicked.
Javascript also sends the assosiated event as parameter and in this way you can get id by using event utilities. You can get the clicked button id as below. By getting that id I think you can also get the associated row. After that you can delete the row.
doDelete(event){
let clickedButtonId = e.target.id;
//get row id. I think you can get it.
document.removeElement(rowId);
}
Event Delegation
Bind/Register the Ancestor to Event
Dynamically added tags cannot bind to an event handler/listener, only tags that have existed since the page was loaded can. So for multiple dynamically added tags such as the buttons you have to find an ancestor tag that they all share incommon and bind it to whatever event you need to listen for. For your buttons it can be the closest ancestor table* (recommended) to the furthest window:
// On-event property. ALWAYS PASS THE EVENT OBJECT
table.onclick = function(event) {...
OR
// Event Listener. Abbreviating the [Event Object][2] is OK, but you must be consistent.
table.addEventListener('click', function(e) {...
Do not use on-event attributes <button onclick="func()"...
✱Technically the closest ancestor is tbody even if you didn't add it to the table, the browser will add it by default.
Use Event.target and Event.currentTarget Properties
Remember to pass the Event Object because you'll need it to...
...find out which button you actually clicked with event.target property.
...get a reference to the table with event.currentTarget property.
...possibly prevent default behavior such as stopping a form from submitting to a server with event.preventDefault() method.
Review the demo, it will have specific details on the event handlers.
Demo
Details commented in demo
var table = document.querySelector("table");
document.forms[0].onsubmit = fillData;
/*
This onevent property handler has two functions note it is bound
to the table NOT the buttons.
There's two conditionals and they only focus on classNames of
either .del or .edit. Once it's determined if the clicked tag has
one of these classes then the appropriate function is called.
If neither class was clicked there's no opportunity for anything
else to act on the click because both conditionals end with
return false thereby terminating the event handler.
*/
table.onclick = function(e) {
if (e.target.className === 'del') {
delRow(e);
return false;
}
if (e.target.className === 'edit') {
editRow(e);
return false;
}
};
function fillData(e) {
var ui = e.target.elements;
e.preventDefault();
var idx = table.rows.length;
var row = table.insertRow();
row.id = 'r-' + idx;
var cell1 = row.insertCell(0);
var data1 = ui.title.value;
cell1.textContent = data1;
var cell2 = row.insertCell(1);
var data2 = ui.desc.value;
cell2.textContent = data2;
var cell3 = row.insertCell(2);
var data3 = ui.chk.checked ? 'Active' : 'Inactive';
cell3.textContent = data3;
var cell4 = row.insertCell(3);
var btns = `
<button class='edit'>📝</button>
<button class='del'>❌</button>`;
cell4.innerHTML = btns;
}
/*
Reference the .closest() row from clicked button
Get that row's id and split() it at the dash and pop() the number.
Then get a reference to the bound ancestor (table) and deleteRow() with the new number you just got.
*/
function delRow(e) {
var row = e.target.closest('tr');
var idx = row.id.split('-').pop();
e.currentTarget.deleteRow(idx);
}
/*
Same as before get the index number from the closest row's id.
Reference the table and use the .rows property and number.
This reference will now allow you to use the .cells property.
Use the .cells property to toggle the contenteditable attribute
on the first three cells.
*/
function editRow(e) {
var row = e.target.closest('tr');
var idx = row.id.split('-').pop();
var R = e.currentTarget.rows[idx];
for (let c = 0; c < 3; c++) {
var cell = R.cells[c];
if (cell.hasAttribute('contenteditable')) {
cell.removeAttribute('contenteditable');
} else {
cell.setAttribute('contenteditable', true);
}
}
}
body {
font: 400 16px/25px Consolas;
display: flex;
justify-content: space-between;
}
fieldset {
width: fit-content
}
input,
label,
textarea {
font: inherit
}
input,
label,
button {
display: inline-block;
height: 25px;
}
#title {
width: 27.5ch;
}
#chk {
display: none;
}
#chk+label::after {
content: '\2610';
font-size: 20px;
vertical-align: middle;
}
#chk:checked+label::after {
content: '\2611';
}
[type='reset'] {
margin-left: 5%
}
td {
min-width: 60px;
border-bottom: 1px solid #000;
height: 25px;
}
tr td:last-child {
border-bottom-color: transparent;
}
button {
width: 35px;
text-align: center;
}
<form id='data'>
<fieldset>
<legend>Enter Data</legend>
<input id='title' type='text' placeholder='Title'><br>
<textarea id='desc' rows='3' cols='25' placeholder='Description'></textarea><br>
<input id='chk' type='checkbox'>
<label for='chk'>Active </label>
<input type='reset'>
<input type='submit'>
</fieldset>
</form>
<hr>
<table></table>

Categories