I'm trying to create a way to add entries to my form so that the user can choose how many he wants, but I can't get the remove part working.
JavaScript
var i = 1;
var divContent = document.getElementById('formulario');
//Click to add a field
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
function remove(div1, div2){
var div = document.getElementById(div1);
var div2 = document.getElementById(div2);
div.remove();
div2.remove();
i--;
}
And now the HTML
<form>
<h4 class="card-tittle text-center">Taxas</h4>
<div id="formulario" class="form row align-items-start">
<div class="mb-1 col-3" id="0">
<label for="nomeTx0" class="form-label">Nome</label>
<input type="text" class="form-control" id="nomeTx0" name="nomeTx0" required>
</div>
<div class="mb-1 col-3" id="0">
<label for="taxa0" class="form-label">Valor</label>
<input type="float" class="form-control" id="taxa0" name="taxa0" required>
</div>
</div>
+ adicionar campo
<div class="mb-1 col-lg-12" style="text-align: center;">
<button class="btn btn-primary col-5" id="Enviar" type="submit" text="Enviar">Adicionar Taxas</button>
</div>
</form>
ID="taxa"+i but when I call the remove(); error is printed to me saing the variable is null.
This is really not the right approach in the first place.
Your fundamental problem is that you are relying on ids to know what element(s) to add and remove and this is leading you to concatenate an id onto dynamically created elements, made from long strings with variables concatenated into them. In reality, you should avoid ids whenever possible as they make your code very brittle and don't scale well.
This is a perfect use for the HTML <template> element. As you can see from the re-worked code below, all ids have been removed - - you don't need them. Additionally, instead of long strings with a variable concatenated into it, you just need to copy/clone the template whenever you need one. Then, you can use "event delegation" and smartly organized HTML to just set up a single click event on a master wrapper element, where the actual element that was clicked (the event.target) can be checked. If it was a remove button, then just remove the entire wrapper that is the nearest ancestor to the remove button that was clicked.
You can now add and remove as many items as you like with no need for an id or counting variables!
// Get a reference to the template, outer div and the add "button"
const template = document.querySelector("template");
const wrapper = document.querySelector(".wrapper");
const add = document.querySelector(".add");
// Set up the add event in Javascript, not with inline HTML
add.addEventListener("click", function(event){
var clone = template.content.cloneNode(true); // Clone the template
wrapper.appendChild(clone);
});
// Set up a wrapper level click event that any clicks within it will bubble up to
wrapper.addEventListener("click", function(event){
// Test to see if it was remove "button" that was clicked
if(event.target.classList.contains("remove")){
// Just remove the closest ancestor div that holds that particular group
// and remove it.
event.target.closest("div.templateWrapper").remove();
}
});
.mb-1.col-lg-12 {
text-align:center;
}
.mb-1.col-3 {
margin:2px;
}
.add, .remove {
cursor:pointer;
color:blue;
}
.labelName { display:inline-block; width:3em; }
/* This is just to better see the groups */
.templateWrapper, .form {
background-color:aliceblue;
padding:5px;
margin:8px;
}
<!-- This will not initially be shown on the page.
It will be used to copy from when/if needed. -->
<template>
<div class="templateWrapper">
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Nome</span>
<input type="text" class="form-control" name="nomeTx" required>
</label>
</div>
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Valor</span>
<!-- An input does not have a type=float -->
<input class="form-control" name="taxa" required>
</label>
</div>
<span class="remove">- Remover campo</span>
</div>
</template>
<form>
<h4 class="card-tittle text-center">Taxas</h4>
<!-- Hyperlinks are for navigation, not JavaScript click hooks. Any visible element
supports a click event. Use span and div for generic clickable inline or block
elements that need to have click event handlers. -->
<span class="add">+ adicionar campo</span>
<div class="wrapper">
<div class="form row align-items-start">
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Nome</span>
<input type="text" class="form-control" name="nomeTx0" required>
</label>
</div>
<div class="mb-1 col-3">
<label class="form-label"><span class="labelName">Valor</span>
<input type="float" class="form-control" name="taxa0" required>
</label>
</div>
</div>
</div>
<div class="mb-1 col-lg-12">
<!-- A button does not have a "text" attribute -->
<button class="btn btn-primary col-5" type="submit">Adicionar Taxas</button>
</div>
</form>
You would like to pass the ids of the html elements to function remove , instead you pass something else.
Try this:
function remove(d1, d2){
//what are passing to function... id , or something else ?
console.log(d1,d2);
// now I force the arguments passed to function to a valid value id for test
var a = document.getElementById('div1'); // id is div1
var b = document.getElementById('div21'); // id is div21
//Ask to parentNode to remove his child
a.parentNode.removeChild(a);
b.parentNode.removeChild(b);
i--;
}
The problem in your code is that you don't pass a string to the remove function but instead you pass the whole element. That is why document.getElementById can't find anything because it expects a string as a parameter. I refactored you code a bit and also when removing the fields the link - Remover campo stayed and was not deleted. I fixed that as well by passing a third argument to the remove function.
var i = 1;
var divContent = document.getElementById('formulario');
//Click to add a field
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
function remove(div1, div2, link){
var div = document.getElementById(div1);
var div2 = document.getElementById(div2);
var link = document.getElementById(link);
divContent.removeChild(div);
divContent.removeChild(div2);
divContent.removeChild(link)
i--;
}
The easiest way to do your code working it change your function "cria". (it's not the best option)
You miss ' '.
You have this.
onclick="remove(div'+i+',div2'+i+')"
You need this.
onclick="remove(\'div'+i+'\',\'div2'+i+'\')"
Javascript just doesn't understand that these parameters are strings.
And the full function "cria" after changes.
function cria() {
//This add a HTML Inputs and divs who the ID is variable how the 'i' is increasedf
document.getElementById('formulario').innerHTML += '<div class="mb-1 col-3" id="div'+i+'"><label for="nomeTx0" class="form-label">Nome</label><input type="text" class="form-control" id="nomeTx'+i+'" name="nomeTx'+i+'" required></div><div class="mb-1 col-3" id="div2'+i+'"><label for="taxa'+i+'" class="form-label">Valor</label><input type="float" class="form-control" id="taxa'+i+'" name="taxa'+i+'" required></div>- Remover campo';
i++;
}
Related
Something strange bug is going on in my code. I want to use HTML template tag with jQuery, because all the rest of my code is jQuery, but I only found JavaScript examples with it. I tried to "translate" from JavaScript to jQuery, this is what I came up with.
$.getJSON( "../Controller/ControllerBookstore.php?show_books=true", function( data ) {
$.each( data, function( index, value ) {
// let clone = document.getElementById('table-template').content.cloneNode(true);
// clone.querySelector('#id').innerText = value.id;
// clone.querySelector('#author').innerText = value.author;
// clone.querySelector('#title').innerText = value.title;
// clone.querySelector('#isbn').innerText = value.isbn;
let clone = $("#table-template").clone(true);
$("#id",clone).text(value.id);
$("#author",clone).text(value.author);
$("#title",clone).text(value.title);
$("#isbn",clone).text(value.isbn);
//$(".container").append(clone);
$("#header").append(clone);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div id="myAlert" class="alert alert-success collapse">
<span id="alert-text"></span>
<a id="alert-close" class="close" href="#" aria-label="Close">×</a>
</div>
<div class="row" id="header">
<div class="col"><h5>ID</h5></div>
<div class="col"><h5>Author</h5></div>
<div class="col"><h5>Title</h5></div>
<div class="col"><h5>ISBN</h5></div>
<div class="col"><h5>Action</h5></div>
</div>
<template id="table-template">
<div class="row">
<div class="col" id="id"></div>
<div class="col" id="author"></div>
<div class="col" id="title"></div>
<div class="col" id="isbn"></div>
<div class="col buttons">
<button class='btn btn-info edit'>Edit</button>
<button class='btn btn-danger delete'>Delete</button>
</div>
</div>
</template>
<div class="row justify-content-center" >
<form action="" class="col-4">
<input id = "id-box" type="hidden" name="id">
<div class="form-group row">
<label class="col-4">Author</label>
<input id = "author-box" type="text" class="form-control col-8" name="author" placeholder="Enter the author of the book">
</div>
<div class="form-group row">
<label class="col-4">Title</label>
<input id = "title-box" type="text" class="form-control col-8" name="title" placeholder="Enter the title of the book">
</div>
<div class="form-group row">
<label class="col-4">ISBN</label>
<input id = "isbn-box" type="text" class="form-control col-8" name="isbn" placeholder="Enter the ISBN of the book">
</div>
<div class="form-group row">
<button id = "submit" type="submit" name="save" class="btn btn-primary col-12">Save</button>
</div>
</form>
</div>
</div>
For some reason the JavaScript code I commented out works, but it only appends "clone" to my ".container" correctly, on the next line below the form. However I want to attach it to my ".header", but it attaches next to the header, not below it. The jQuery code doesn't do anything, it doesn't attach my "clone" anywhere.
I hope I was clear. Could you please help me to find the reason of the bugs?
A few changes are needed:
The id value of the template has a hyphen which must be escaped in the selector. Two backslashes are needed in the string literal; the first is needed to actually get a backslash in the string. The remaining one will be interpreted by the selector.
Clone the row element within the template, not the template itself. However, jQuery will not know of a DOM within the template tag, so you could just take the HTML content instead of cloning, and then turn that into a jQuery object again (which produces the DOM for it).
Insert the clone just before the template
Code:
let clone = $($("#table\\-template").html()); // <--------
$("#id",clone).text(value.id);
$("#author",clone).text(value.author);
$("#title",clone).text(value.title);
$("#isbn",clone).text(value.isbn);
$("#table-template").before(clone); // <------
As others have commented, id attributes should have unique values, so your template content cannot have id properties (since it gets cloned). Use class attributes instead.
jQuery bug
Hello my friend. You are cloning the incorrect element, because your create a clone of template with the id #table-template. Please, make this change to your code:
...
let clone = $("#table-template").html();
...
The other thing, the cloned code appears next to #header and not below it because you are using a .row class. I propose to create a div below the #header, with the id="body" and append the new content inside:
...
// $("#header").append(clone);
-> $("#body").append(clone);
...
Thanks for the example.
But I don't change the id of the "collapse" div.
The rest of the objects are cloned normally.
<template id="facilities_template">
<div class="collapse">
<div class="form-check icon-check">
<input class="form-check-input" type="checkbox">
<label class="form-check-label font-14" id="facilities_name" ></label>
<i class="icon-check-1 far fa-square color-gray-dark font-20"></i>
<i class="icon-check-2 fa fa-check-square font-20 color-green-dark"></i>
</div>
<div class="mb-3"></div>
</div>
</template>
JavaScript:
let cloneFacility = $($('#facilities_template').html());
$('#facilities_name', cloneFacility).text(value.name);
$('#facilities_name', cloneFacility).attr('data-facility-id', value.id);
$('#collapse', cloneFacility).attr('id','collapse'+ value.id)
$('#facilities_template').before(cloneFacility);
$('#faсility_filter').append(cloneFacility);
I have a (+) sign and a (-) sign. If the user clicks on the + sign than whatever their in the row will automatically get generated the same with new id.
Now when user click on + sign than div id and text box under it will get changed.
Code below for div as follows:
<div class="row" id="Div0">
<div class="col-md-3">
<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control" id="txtLastName0" placeholder="Enter name" required="required" />
</div>
</div>
<div class="col-md-9"></div>
</div>
Now when user clicks on + sign the new row with text box with new id txtLastName1 will get generated.
Now on click of + sign how do i get new id of textbox and a div with new row.
Div1 and textbox1 will get generated
Basically what you should do is, keep the markup you want to generate as a template in your DOM and when user clicks the "Add" button, clone this markup and append to the DOM (to a container div). then update the Id's of the input and divs as needed.
A simply sample would be like
<div class="row" id="template" style="display: none;">
<div class="col-md-3">
<div class="form-group">
<label for="name">Last Name</label>
<input type="text" class="form-control lname" id="txtLastName"
placeholder="Enter name" required="required" />
</div>
</div>
<div class="col-md-9"></div>
</div>
<button id="btnAdd">+</button>
<div id="container"></div>
Now wire up a click event handler to the Add/+ button. You may use the jQuery clone() method.
$(function () {
$("#btnAdd").click(function(e) {
e.preventDefault();
var childCount = $("#container").children().length;
var c = $("#template").clone().show();
c.attr("id", "Div" + childCount );
c.find(".lname").attr('id', 'txtLastName' + childCount);
$("#container").append(c);
});
});
For deleting, you can add a event handler on the delete button and remove the specific div from dom. jQuery remove() method will do it. Use closest() and find as needed to get the correct div to remove.
Here is a working simple jsbin sample for your reference
I'd like to finish this function, but I'm not sure where I'm going wrong.
I'm trying to add and remove divs called 'milestones' with various inputs inside of them with jquery. I have the 'add milestone' button working currently. Each milestone div has a 'delete milestone' button that, when clicked, should delete that div entirely. For some reason I'm not able to interact with the buttons inside those dynamically crated divs.
I'm also trying to incremement the Milestone #.
The HTML
<!-- Milestone Title -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Milestone Title</label>
<input class="form-control" type="text" name="Milestone[0]
[MilestoneTitle]" placeholder="Dusty Bench" required />
</div>
</div>
</div>
<!-- Milestone Deadline -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Deadline</label>
<input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/>
</div>
</div>
</div>
<!-- Milestone Description -->
<div class="row">
<div class="col-md-5">
<div class="form-group">
<label class="control-label">Milestone Description</label>
<textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required>
</textarea>
</div>
</div>
</div>
<div class="additional-milestones">
</div>
<!-- + Add Milestone Button -->
<div class="row">
<div class="col-md-5">
<a class="add-milestone btn btn-primary">
+ Add Another Milestone
</a>
</div>
</div>
<!-- End Set Milestones Tab #2 -->
The JQuery
$().ready(function() {
//Max amount of milestones
var max_milestone = 5
// Initial Milestone Count
var x = 1;
//function for add milestone button
//if button.add-milestone is clicked
$('.add-milestone').click(function(e) {
//console.log('dope')
e.preventDefault();
if (x < max_milestone) {
x++; //increment milestones
$('.additional-milestones').before('<h4>Milestone #1</h4><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Title</label><input class="form-control" type="text" name="Milestone[0][MilestoneTitle]" placeholder="Dusty Bench" required /></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Deadline</label><input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Description</label><textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required> </textarea></div></div></div><div class="row"><div class="col-md-5"><a class="delete-milestone btn btn-danger"> - Delete This Milestone</a></div></div><hr>');
}
});
//then
//populate with milestone form with Milestone+Next Milestone Number
//Delete Milestone
$('.delete-milestone').click(function() {
console.log('dope')
//$(this).parent().remove();
});
});
The CSS
.btn {
border-width: 1px;
background-color: transparent;
font-weight: 400;
opacity: 0.8;
border-color: #888888;
color: #888888;
}
.btn-primary {
border: solid 1px #447DF7;
}
.btn-danger {
border: #FB404B solid 1px;
}
My JS fiddle is here.
Thank you!
What is going wrong with your code is that you are attaching event handler to elements with class .delete-milestone initially in your code, whereas these elements are added dynamically. As a result when the code is executed, it tries to attach a event handler to the elements with class .delete-milestone but actually there are no elements present.
You can instead attach event handler after the element is injected into the HTML. Have a look at this modified jsfiddle of your code.
Or you can also make use of onclick attribute.
And for incrementing the milestone #, you can have a separate variable to keep a count. Increment when element is added and decrement when it is deleted.
Your javascript defines the click event on .delete-milestones before it exists. You have to call .on click after it was added to the dom.
Try this:
$('.add-milestone').click(function() {
$newMilestone = $('<h4>Milestone #1</h4><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Title</label><input class="form-control" type="text" name="Milestone[0][MilestoneTitle]" placeholder="Dusty Bench" required /></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Deadline</label><input type="text" class="form-control datetimepicker" name="Milestone[0][MilestoneEndDate]" placeholder="Deadline" required/></div></div></div><div class="row"><div class="col-md-5"><div class="form-group"><label class="control-label">Milestone Description</label><textarea class="form-control" id="exampleTextarea" name="Milestone[0][Description]" rows="3" required> </textarea></div></div></div><div class="row"><div class="col-md-5"><a class="delete-milestone btn btn-danger"> - Delete This Milestone</a></div></div>');
$('.additional-milestones').before($newMilestone);
$newMilestone.find('.delete-milestone').on('click', function() {
console.log('dope')
});
});
First, the code waits for the document to become ready. It's going to run that code you posted once, at the beginning.
When the document is ready, $('.delete-milestone') will get a list of buttons that are currently on the page. .click(...) will add event listeners to them.
Later, if you add more buttons, no code adds event listeners to them.
Here are two ideas on how to make it work:
When you create a button, add an event listener.
Use delegated events to handle events on anything that matches a selector that bubbles up to a common ancestor.
I'm about lose my mind with this problem. No form of jQuery selector seems to work in dynamically finding any elements above the link. I'm trying to access an element above the link and hide it. Using things like parent(), prev(), before(), closest(), ect. will show a non-null object but it won't respond to the hide() method.
<div class="row">
<div class="col-xs-5">
<div id="test_fields">
<li id="test_input" class="string input optional stringish">
<label class="label" for="test_input">Ingredient name</label>
<input type="text" name="test_input" value="afsfasf" id="test_input">
</li>
</div>
<input type="hidden" id="recipe_recipe_ingredients_attributes_0__destroy" name="recipe[recipe_ingredients_attributes][0][_destroy]">
Remove Ingredient
</div>
</div>
function remove_fields(link)
{
$(link).prev("input[type=hidden]").val('1'); // this doesn't work
var divToHide = $(link).prev('div');
$(divToHide).hide() // this doesn't work
//$('#test_fields').hide(); //this works
}
Try replacing the link as below:
Remove Ingredient
I'm not sure. But maybe this is the problem. Because I remember that I have had problem with 'this'previously and when I replaced that, it performed the job.
you can try .closest() and .find()
function remove_fields(link) {
$(link).closest('div[class^="col-xs"]').find("input[type=hidden]").val('1');
var div_to_hide = $(link).closest('div[class^="col-xs"]').find('#test_fields');
$(div_to_hide).hide();
//$('#test_fields').hide(); //this works
}
You can't change hidden input's "value" attribute by using .val(). You need to use:
$(link).prev("input[type=hidden]").attr('value', '1');
As I'm not really sure what do you want to do with this input, I'll just let it go like this.
.prev() fn goes only one previous element in the structure. As input is a <a>'s previous element, you can't select div like that. You can use .siblings() for instance.
$(link).siblings('div').hide();
If you break the code in pieces, it gets easier.
First I took the 'Link', from it I grabbed the nearest div above it, then I picked up the input.
I did not make many changes to your code.
function remove_fields(link)
{
var $link =$(link);
var $divToHide = $link.closest('div');
$divToHide.find("input[type='hidden']").val('1');
$divToHide.hide()
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="row">
<div class="col-xs-5">
<div id="test_fields">
<li id="test_input" class="string input optional stringish">
<label class="label" for="test_input">Ingredient name</label>
<input type="text" name="test_input" value="afsfasf" id="test_input">
</li>
</div>
<input type="hidden" id="recipe_recipe_ingredients_attributes_0__destroy" name="recipe[recipe_ingredients_attributes][0][_destroy]">
Remove Ingredient
</div>
</div>
I'm making basic GPA calculator using Javascript.
Here is my code:
<div class="list">
<div class="row">
<div class="col col-50">Subject 1</div>
<div class="col"><input type="text" name ="GR1" placeholder="Grade"></div>
<div class="col"><input type="tel" name="CR1" placeholder="Credits"></div>
</div>
<div class="row">
<div class="col col-50">Subject 2</div>
<div class="col"><input type="text" name ="GR2" placeholder="Grade"></div>
<div class="col"><input type="tel" name ="CR2" placeholder="Credits"></div>
</div>
<button class="button button-positive">
Add Another Field //it can add uptop 10 fields
</button>
</div>
It will increment the same div series while incrementing the input name up to 10 fields. User can click Add Another Field and add a new div field.
In every div field, it only changes the subject and input fields' name with an incrementation of 1.
Question:
What is the best way to achieve this without duplicating the same thing over and over? Or do I need to first create 10 div forms and hide all and show them one by one upon each click? Please give me example.
Here is a solution that is in pure Javascript that will allow you to add up to 10 "field blocks". In the HTML file, put:
<div id="list">
<button onclick="addRow()">Add another field</button>
</div>
And here's the Javascript function to add a new row, and initialise the two first row:
<script type="text/javascript">
window.onload = function() {
addRow();
addRow();
};
function addRow() {
var element = document.getElementById('list');
var nextId = element.childElementCount;
if (nextId <= 10) {
var div = document.createElement('div');
div.setAttribute('class', 'row');
div.innerHTML = '<div class="col col-50">Subject ' + nextId + '</div><div class="col"><input type="text" name ="GR' + nextId + '" placeholder="Grade"></div><div class="col"><input type="tel" name="CR' + nextId + '" placeholder="Credits"></div>';
element.insertBefore(div, element.getElementsByTagName('button')[0]);
}
}
</script>
You can try it online on the following fiddle: https://jsfiddle.net/w82t30r4/
Try jQuery's clone (read about it here)
$(document).ready(function(){
$row = $(".row").clone();
$("button").click(function(){
$(".list").append($row.clone());
})
})
What's happening is that I clone the row to start with (before any data is in it). Then I add a clone of that clone to .list when the button is clicked.