how submit dynamic forms - javascript

I have the following problem that I cannot solve
is a form of this type
in HTML
<button (click)="addform()">agregar formulario</button>
<div class="conten-form">
<div class="MyForm">
<label>Nombre</label>
<input type="text" class="name">
<label>descripcion</label>
<input type="text" class="description">
<label>Foto</label>
<div class="img-cont">
<div class="img">
<img src="{{img}}">
</div>
<div class="addimg">
<input type="file" (change)="createimg($event)">
</div>
</div>
</div>
<div class="AddForm"></div>
<input type="buttom" value="enviar" (click)="enviarform()">
</div>
in TS
img : any; //to show
imgfile : any; //to send it through the form
constructor(...){...}
addform(){
let addform = document.querySelectorAll('.AddForm');
let myform = document.querySelectorAll('.MyForm');
let cloneform = myform.cloneNode(true);
addform.appendChild(cloneform);
}
createimg(event){
this.imgfile = event.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(this.imgfile);
reader.onload = () => {
this.img = reader.result;
};
}
enviarform(event){
let nombre = document.querySelectorAll(".name");
let description = document.querySelectorAll(".description");
let formdata = new FormData;
//add all inputs class name
for (let i = 0; i < nombre .length; i++) {
let stringname = (nombre.[i] as HTMLTextAreaElement).value;
formdata.append('name',stringname);
}
//add all inputs class description
for (let i = 0; i < description.length; i++) {
let stringdescription = (description.[i] as HTMLTextAreaElement).value;
formdata.append('description',stringdescription );
}
//send the form
}
As you will see, I can add more forms with the add button, cloning the first one, the part of the text inputs I have solved to add as many as I want, but in the part of the inputs for the image I have no idea how to do it, the operation is that after attaching an image to the input, I get a preview image of what I am going to attach in the div.class = img, this makes it only work in the first form, and it no longer works in the forms that I add dynamically, as it could fix this issue? taking into account that I have to send it through the formdata in a single shipment, I thank you in advance.

you should not use
let addform = document.querySelectorAll('.AddForm');
let myform = document.querySelectorAll('.MyForm');
let cloneform = myform.cloneNode(true);
at all. to add forms. instead you should create a separate component for each form.
check my question earlier on stackoverflow to get what I mean.
more specifically check the answer stackblitz.

Related

How to clone or create a nested DOM node and change all its containing id values according to a current id?

I need to display some numbers, strings from a class named Student, but i can't figure it out how i can change the id from children element. I have to use JavaScript.
what i tried to do:
class Student{
static count = 0;
constructor(nume, prenume, data_nasterii, foaie_matricola){
this.IdClasa = ++Student.count;
//definirea atributelor
this.nume = nume;
this.prenume = prenume;
this.data_nasterii = data_nasterii;
this.foaie_matricola = foaie_matricola;
}
afiseazaVarsta(){
}
afiseazaNotele(){
}
calculeazaMedia(){
}
adaugaNota(nota_noua){
}
}
var Stud = [new Student("Name", "Name1", "2000.01.01", "0123123"),
new Student("Green", "Blue", "2022/12.12", "321321")];
function afisareStudenti(){
let i = 0; let bol = false;
for(let x=1; x<=Student.count; x++) {
console.log(document.getElementById("AfisareStudenti"+x)==null);
if(document.getElementById("AfisareStudenti"+x)==null)
{
i = x;
bol = true;
break;
} else {
bol = false;
}
}
if((i<=Student.count)&&(bol==true)){
for(i; i<=Student.count; i++) {
console.log("i="+i);
var div = document.querySelector('#AfisareStudenti1');
var divClone = div.cloneNode(true);
console.log(divClone);
divClone.id = 'AfisareStudenti'+(i);
div.after(divClone);
var NumeStud = document.getElementById("NumeStudent"+(i-1));
var PrenumeStud = document.getElementById("PrenumeStudent"+(i-1));
var dataNastStud = document.getElementById("intData"+(i-1));
var FoaiaMatStud = document.getElementById("FoaiaMatStud"+(i-1));
NumeStud.id = "NumeStudent"+(i);
PrenumeStud.id = "PrenumeStud"+(i);
dataNastStud.id = "intData"+(i);
FoaiaMatStud.id = "FoaiaMatStud"+(i);
}
}
}
and this is the html file(the div that i want to clone):
<!--AFISARE-->
<div id="AfisareStudenti1">
<h2> Afisare Student 1</h2>
<label>Ce student doriti sa modificati? </label>
<form>
<label>Nume:</label><br>
<input type="text" id="NumeStudent1"><br>
<label>Prenume:</label><br>
<input type="text" id="PrenumeStudent1"><br>
<label>Data Nasterii:</label><br>
<input type="date" id="intData1"><br>
<label>Foaie matricola:</label><br>
<input type="text" id="FoaiaMatStud1"><br><br>
<input class="butoane" type="submit" value="Afisare"
onclick="afisareMeniuAfisStudenti()">
</form>
</div>
the class is saved in a dynamic array (could be n object of the class) so i have to make somehow to display the information dynamic. My version changes the id from all elements with the same id (every incrementation of i, the idnumber from id is incremented also). I tried to create that div with document.createElement but is impossible(at least for me) xD . I started coding in javascript 2 days ago, so please take it slow on me :(
I think i found the problem, but it doesn't solve it. (i need to put (i-1) when calling for getting the ids). (Newbie mistake)
Having commented ...
"I have the feeling that if provided with the broader picture the audience could be of much more help since the OP could be provided back with leaner/cleaner and better maintainable approaches."
... I nevertheless hereby lately provide a template-based approach which, besides supporting the OP's id based querying of student-items, is also easier to read and to maintain.
The code provided within the example-code's main function does not just implement the usage of the template-based node-creation via template literals and DOMParser.parseFromString but also prevents the default behavior of each student-form's submit-button by making use of event-delegation.
function createStudentElement(studentId) {
const markup =
`<div class="student-item" id="AfisareStudenti${ studentId }">
<h2> Afisare Student ${ studentId }</h2>
<label>Ce student doriti sa modificati? </label>
<form>
<label>Nume:</label><br>
<input type="text" id="NumeStudent${ studentId }"><br>
<label>Prenume:</label><br>
<input type="text" id="PrenumeStudent${ studentId }"><br>
<label>Data Nasterii:</label><br>
<input type="date" id="intData${ studentId }"><br>
<label>Foaie matricola:</label><br>
<input type="text" id="FoaiaMatStud${ studentId }"><br><br>
<input
class="butoane" type="submit" value="Afisare"
onclick="afisareMeniuAfisStudenti(${ studentId })"
>
</form>
</div>`;
const doc = (new DOMParser).parseFromString(markup, 'text/html');
return doc.body.removeChild(doc.body.firstElementChild);
}
// the button click handler.
function afisareMeniuAfisStudenti(studentId) {
console.log({ studentId })
}
function main() {
const itemsRoot = document.querySelector('.student-items');
// - prevent any form-submit by making use of event-delegation.
itemsRoot.addEventListener('submit', evt => evt.preventDefault());
// - just for demonstration purpose ...
// ... create student-items from a list of student IDs.
[1, 2, 3, 4, 5].forEach(studentId =>
itemsRoot.appendChild(
createStudentElement(studentId)
)
);
}
main();
.as-console-wrapper { left: auto!important; width: 50%; min-height: 100%; }
<div class="student-items"></div>
Tom's answer above is what you want for the element id problem that you asked about.
For your code in particular, you are going to have a couple other problems:
Because the final input is type="submit", its going to reload the page by default when it is clicked. The name of the "onclick" function also needs to match the function you defined (afisareStudenti).
You have:
<input class="butoane" type="submit" value="Afisare" onclick="afisareMeniuAfisStudenti()">
Change this to:
<input class="butoane" type="submit" value="Afisare" onclick="afisareStudenti(event)">
Now, when you click that button, it will call the afisareStudenti function and pass in the "event". So if you change:
function afisareStudenti(){
let i = 0; let bol = false;
to:
function afisareStudenti(event){
event.preventDefault()
let i = 0; let bol = false;
This will correctly call your function, and prevent the "default" action of that submit button from reloading the page.
To change the id attribute of children elements, you could use Element.querySelector() on divClone.
Because if you use Document.querySelector() or Document.getElementById() you will get the first element that matches your selector (i.e.children of div#AfisareStudenti1).
let i = 2;
var div = document.querySelector('#AfisareStudenti1');
var divClone = div.cloneNode(true);
divClone.id = 'AfisareStudenti'+(i);
divClone.querySelector("h2").innerText = "Afisare Student " + i;
var NumeStud = divClone.querySelector("#NumeStudent1");
var PrenumeStud = divClone.querySelector("#PrenumeStudent1");
var dataNastStud = divClone.querySelector("#intData1");
var FoaiaMatStud = divClone.querySelector("#FoaiaMatStud1");
NumeStud.id = "NumeStudent"+(i);
PrenumeStud.id = "PrenumeStud"+(i);
dataNastStud.id = "intData"+(i);
FoaiaMatStud.id = "FoaiaMatStud"+(i);
div.after(divClone);
<div id="AfisareStudenti1">
<h2> Afisare Student 1</h2>
<label>Ce student doriti sa modificati? </label>
<form>
<label>Nume:</label><br>
<input type="text" id="NumeStudent1" /><br>
<label>Prenume:</label><br>
<input type="text" id="PrenumeStudent1" /><br>
<label>Data Nasterii:</label><br>
<input type="date" id="intData1" /><br>
<label>Foaie matricola:</label><br>
<input type="text" id="FoaiaMatStud1" /><br><br>
<input class="butoane" type="submit" value="Afisare" onclick="afisareMeniuAfisStudenti()" />
</form>
</div>

Set order of uploaded images (JS, PHP)

My goal is to get a way to upload multiple images and also set in which order they should be displayed later on (it's for an ecommerce website). I have an idea which seems to work. But I would like to know if there is a better way of doing this. Here is my code. Let's assume the number of images is limited to 3.
One input for multiple images.
Preview images and make them sortable.
Store the order in hidden inputs.
Store the images in the filesystem.
Add the path and order index of every image to the database.
HTML
<!-- One input for all images -->
<input type="file" name="images[]" onchange="previewImages(this)" multiple>
<!-- This <div> will be made sortable with SortableJS -->
<div id="preview-parent">
<div class="preview">
<!--
Hidden input to save the order of images.
The idea is to have images_order[name_of_image] = index.
name_of_image and index will be set with JavaScript
-->
<input class="order-input" type="hidden" name="images_order[]" value="0">
<!-- <img> is for preview -->
<img class="preview-image" src="" alt="Image1">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="1">
<img class="preview-image" src="" alt="Image2">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="2">
<img class="preview-image" src="" alt="Image3">
</div>
</div>
JavaScript
function previewImages(input) {
// First I take the images
var file = input.files
// Then I go through each of them. This code is based on the explanation
// from https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
for (var i = 0; i < file.length; i++) {
let preview = document.querySelectorAll('.preview-image')[i]
let reader = new FileReader()
reader.onloadend = function () {
preview.src = reader.result
}
if (file[i]) {
reader.readAsDataURL(file[i])
// In addition to previewing images I take their names
// and put them into my hidden inputs
let order_inputs = document.querySelectorAll('.order-input')
order_inputs[i].name = 'images_order[' + file[i].name +']'
} else {
preview.src = ""
}
}
}
// I make the images sortable by means of SortableJS
var el = document.getElementById('preview-parent')
new Sortable(el, {
animation: 150,
// This function updates the values of my hidden inputs
// every time a change is made
onEnd: function (event) {
let order_inputs = document.querySelectorAll('.order-input')
for (var i = 0; i < order_inputs.length; i++) {
order_inputs[i].value = [i]
}
}
})
function previewImages(input) {
var file = input.files
for (var i = 0; i < file.length; i++) {
let preview = document.querySelectorAll('.preview-image')[i]
let reader = new FileReader()
reader.onloadend = function () {
preview.src = reader.result
}
if (file[i]) {
reader.readAsDataURL(file[i])
let order_inputs = document.querySelectorAll('.order-input')
order_inputs[i].name = 'images_order[' + file[i].name +']'
} else {
preview.src = ""
}
}
}
var el = document.getElementById('preview-parent')
new Sortable(el, {
animation: 150,
onEnd: function (event) {
let order_inputs = document.querySelectorAll('.order-input')
for (var i = 0; i < order_inputs.length; i++) {
order_inputs[i].value = [i]
}
}
})
#preview-parent {
display: flex;
gap: 1rem;
margin: 1rem;
}
.preview {
height: 100px;
width: 100px;
padding: 1rem;
border: 1px solid grey;
}
.preview img {
max-height: 100%;
max-width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/sortablejs#latest/Sortable.min.js"></script>
<input type="file" name="images[]" onchange="previewImages(this)" multiple>
<div id="preview-parent">
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="0">
<img class="preview-image" src="" alt="Image1">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="1">
<img class="preview-image" src="" alt="Image2">
</div>
<div class="preview">
<input class="order-input" type="hidden" name="images_order[]" value="2">
<img class="preview-image" src="" alt="Image3">
</div>
</div>
I haven't figured out yet how to limit the number of submitted images. But I saw a number of discussions about that, so I hope it will not be a problem.
PHP. The next step is done by means of PHP (CodeIgniter 4). I take the images, store them in my filesystem. Also I add the path to every image and its order index (taken from the hidden input) to the database. Later when a user will open certain product, the data will be taken from the database and ordered by order index. So basically my controller has this:
// I inserted the product before and can get its id
$product_id = $this->products->insertID();
// This line is just for reference, I actually create $input earlier
$input = $this->request->getPost();
// Here I take all the images as suggested in CodeIgniter docs
if($imagefile = $this->request->getFiles())
{
foreach($imagefile['images'] as $img)
{
if ($img->isValid() && ! $img->hasMoved())
{
// Store each image
$name = $img->getName();
$img->move('assets/images/'.$product_id, $name);
// Add info about the image to the database
$data = [
'product_id' => $product_id,
'path' => base_url('assets/images/'.$product_id.'/'.$name),
'order_index' => $input['images_order'][$name],
];
$this->imagesModel->insert($data);
}
}
}
It would be perfect if I could upload multiple images at once and then not only reorder them but also be able to replace one of the images. If someone could share any ideas, I will appreciate it very much!
After long time I have returned to this project and made further research. So I would like to share my solution.
So I have decided that a convenient option will be to have only one input for multiple images. The user clicks it and chooses the needed images. If he needs to add more images after this, he clicks the same input again and the new images are added to the list. All images immediately appear as previews with the "delete" button. So if the user needs to replace one image, he can delete it, upload a new image and move it to the needed place.
The PHP code from my post does not require any changes. The work is done in Javascript. There are lots of lines of code, but a big part of them is simply for dynamic generating of previews. Please see the snippet.
// DataTransfer allows updating files in input
var dataTransfer = new DataTransfer()
const form = document.querySelector('#form')
const input = document.querySelector('#input')
input.addEventListener('change', () => {
let files = input.files
for (let i = 0; i < files.length; i++) {
// A new upload must not replace images but be added
dataTransfer.items.add(files[i])
// Generate previews using FileReader
let reader, preview, previewImage
reader = new FileReader()
preview = document.createElement('div')
previewImage = document.createElement('img')
deleteButton = document.createElement('button')
orderInput = document.createElement('input')
preview.classList.add('preview')
document.querySelector('#preview-parent').appendChild(preview)
deleteButton.setAttribute('data-index', i)
deleteButton.setAttribute('onclick', 'deleteImage(this)')
deleteButton.innerText = 'Delete'
orderInput.type = 'hidden'
orderInput.name = 'images_order[' + files[i].name + ']'
preview.appendChild(previewImage)
preview.appendChild(deleteButton)
preview.appendChild(orderInput)
reader.readAsDataURL(files[i])
reader.onloadend = () => {
previewImage.src = reader.result
}
}
// Update order values for all images
updateOrder()
// Finally update input files that will be sumbitted
input.files = dataTransfer.files
})
const updateOrder = () => {
let orderInputs = document.querySelectorAll('input[name^="images_order"]')
let deleteButtons = document.querySelectorAll('button[data-index]')
for (let i = 0; i < orderInputs.length; i++) {
orderInputs[i].value = [i]
deleteButtons[i].dataset.index = [i]
// Just to show that order is always correct I add index here
deleteButtons[i].innerText = 'Delete (index ' + i + ')'
}
}
const deleteImage = (item) => {
// Remove image from DataTransfer and update input
dataTransfer.items.remove(item.dataset.index)
input.files = dataTransfer.files
// Delete element from DOM and update order
item.parentNode.remove()
updateOrder()
}
// I make the images sortable by means of SortableJS
const el = document.getElementById('preview-parent')
new Sortable(el, {
animation: 150,
// Update order values every time a change is made
onEnd: (event) => {
updateOrder()
}
})
#preview-parent {
display: flex;
}
.preview {
display: flex;
flex-direction: column;
margin: 1rem;
}
img {
width: 100px;
height: 100px;
object-fit: cover;
}
<script src="https://cdn.jsdelivr.net/npm/sortablejs#latest/Sortable.min.js"></script>
<form method="post" enctype="multipart/form-data" id="form">
<input type="file" name="images[]" id="input" multiple>
<button type="submit">Save</button>
</form>
<!-- This <div> will be made sortable with SortableJS -->
<div id="preview-parent">
<!-- After upload Javascript will generate previews with this pattern
<div class="preview">
<img src="...">
<button data-index=0>Delete</button>
<input type="hidden" name="images_order['name']" value=0>
</div>
-->
</div>

Get file possibly to string on form submit

So I'm trying to get a file on submit but i got this code snippet that gets it onchange. Truthfully i do not really understand the first line and how it transitions BUT basically i want to turn it to a function i can call when i click submit and without having the eventlistener there as sometimes it does not work unless called on the html form.
const fileSelector = document.querySelector('#a_pic');
fileSelector.addEventListener('change', (event) => {
const fileList = event.target.files;
file = fileList[0];
pic_url = URL.createObjectURL(file);
alert(file)
document.querySelector('#secondcheck').innerHTML = '<img src = "' + pic_url + '">';
a_pic = file;
});
Html code:
<form>
<input class= "form-control" type = "file" name= "a_pic" id= "a_pic" accept= "image/*">
<button type="button" value = "submit" name = "submit" id="submit" onclick = "whatever_the_newfunction_may_be()">submit</button>
Please any explanation is appreciated as i am relatively new green in javascript.
This works fine. There's a variety of ways to handle a form submission, but here's one where you don't use the form's submit event but instead just a button's click event.
const fileSelector = document.querySelector('#a_pic');
let picBlobUrl;
fileSelector.addEventListener('input', (event) => {
const fileList = event.target.files;
file = fileList[0];
picBlobUrl = URL.createObjectURL(file);
});
function submitClicked() {
document.querySelector('#secondcheck').innerHTML = '<img src="' + picBlobUrl + '">';
}
<div>
<input class="form-control" type="file" name="a_pic" id="a_pic">
<button onclick="submitClicked()">submit</button>
</div>
<div id="secondcheck"></div>

Angular 2 How to remove file from files of input type file multiple?

How to remove specific file from files selected with input type with multiple attribute?
<input type="file" (change)="onFileChange($event)" #fileInput multiple>
I want to delete one of the selected file.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
https://jsfiddle.net/Sagokharche/eL3eg6k4/
Do you need it to be impossible to choose? Then use HTML Input file accept property. accept="image/png" for instance.
Or you want it to filter from the input after the user selected it?
Then you should use a custom directive or check for the file types in the ts code upon upload.
EDIT
in that case, in your code:
onFileChange(event) {
const fileList = event.target.files;
console.log("User selected fileList:", fileList)
Array.from(fileList).filter(
item => {
console.log("file mime type:", item['type'])
})
const filesToUpload = Array.from(fileList).filter(
item => { return item['type'] != "application/zip" })
console.log("reduced list:", filesToUpload)
}
Working stackblitz example here.
You can access the inputs FileList-object in .ts side like this:
onFileChange(event) {
console.log(event.srcElement.files);
}
Edit:
If you are looking for a solution how to make dynamic form (add and delete inputs), then have a look at this answer and demo:
Angular 4 Form FormArray Add a Button to add or delete a form input row
In your hmtl code
<div class="row">
<div class="col-md-2 productAddfromImages" *ngFor='let url of imageurls; let i = index'>
<img class="img-fluid" [src]="url.base64String">
<a (click)="removeImage(i)" class="btn btn-xs btn-danger">Remove</a>
</div>
</div>
Remove function
removeImage(i) {
this.imageurls.splice(i, 1);
}
Add Function
onSelectFile(event) {
if (event.target.files && event.target.files[0]) {
var filesAmount = event.target.files.length;
for (let i = 0; i < filesAmount; i++) {
var reader = new FileReader();
reader.onload = (event: any) => {
this.imageurls.push({ base64String: event.target.result, });
}
reader.readAsDataURL(event.target.files[i]);
}
}
}
}
For more details:https://findandsolve.com/articles/how-to-upload-and-remove-multiple-image-using-anular-code-example

Updating Values in HTML output based on changing input

I currently have a set of fields and radio buttons that take in some user input. Here is an example:
<div id="age">
<input type="number" name="age1" value=60>
</div>
I am displaying all the inputted values and want the display to change when the user modifies the input. This is my attempt:
var inputElements = document.querySelectorAll('input');
for(var i = 0, len = inputElements.length ; i < len ; i++) {
inputElements[i].addEventListener('input', updateDisplay());
}
function updateDisplay () {
console.log("test");
var age = document.querySelector('input[name="age1"]').value;
document.getElementById("ageComparison").innerHTML = age;
}
I know that the program enters the method since the "test" message is printed to the console; however, I don't see any change in display according to changes in input. Would appreciate any help.
While creating the eventlistener, you're just calling updateDisplay. Remove the ().
Also, you did not put '#ageComparison' element in your code.
html:
<div id="age">
<input type="number" name="age1" value=60>
</div>
<div id="ageComparison">
</div>
js:
var inputElements = document.querySelectorAll('input');
for (var i = 0; i < inputElements.length; i++) {
inputElements[i].addEventListener('input', updateDisplay);
}
function updateDisplay() {
console.log("test");
var age = document.querySelector('input[name=age1]').value;
document.getElementById("ageComparison").innerHTML = age;
}
Example: https://jsfiddle.net/m6r871t6/
Try avoiding the inner double quotes
var age = document.querySelector('input[name=age1]').value;
try using
inputElements[i].addEventListener('change', updateDisplay())

Categories