Reputation: 122
I'm builtin an web resume-generator to learn how to develop for web. I've made a HTML form which the user can add more fields to add more information's about him. Example: he has more than one professional experience, but the form starts with a single prof-exp field to fill. So he clicks in a "add new exp" button and the JS creates a new field for it. I used the clone()
method from jQuery to do this, but this gives me with the problems I've listed bellow. Also, here's the code I've made:
var index = 0;
$(document).ready(() => {
$("#add-exp").click(() => {
$("#professional").clone().attr("id", "professional" + index++).
appendTo("#professional-info").find("select, input, textarea").val("");
})
})
<!DOCTYPE html>
<html>
<body>
<form action="" method="GET" id="main">
<fieldset id="professional-info">
<legend><h2>professional experience</h2></legend>
<div id="professional">
<label for="level">Nível: <select name="level" id="level" >
<option value="empty">Selecione</option>
<option value="estagio">Estágio</option>
<option value="junior-trainee">Junior/Trainee</option>
<option value="aux-opera">Auxiliar/Operacional</option>
<option value="pleno">Pleno</option>
<option value="senior">Sênior</option>
<option value="sup-coord">Supervisão/Coordenação</option>
<option value="gerencia">Gerência</option>
</select></label>
<label for="position"> Cargo: <input type="text" name="carrer" id="carrer" ></label><br>
<label for="company"> Empresa: <input type="text" name="company" id="company" ></label><br>
<label for="begin"> Início: <input type="month" name="begin" id="begin" ></label>
<label for="break"> Término: <input type="month" name="break" id="break" ></label>
<label for="stl-work"><input type="checkbox" name="stl-work" id="stl-work" >Ainda trabalho aqui</label><br>
<label for="job-desc"> Descrição: <textarea name="job-desc" id="job-desc" placeholder="Conte um pouco sobre o que você fazia lá." cols="40" rows="1"></textarea></label>
<button type="button" id="remove-exp" >Remove this professional xp</button>
</div>
<button type="button" form="main" id="add-exp">Add other professional exp</button>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
The problems are:
Only the divs have dynamic ID's, which causes me the following two other problems;
I don't know how to implement the remove button logic, since I cannot make difference between the 1st button and the other ones from other divs;
Since the labels use their correspondent input ID to make reference, when the user clicks it, they point to the first field inputs;
I hope you guys could understand my problem and help me with it. Also, sorry for my english - i'm learning too. Thank you all!
Upvotes: 1
Views: 2148
Reputation: 43880
Details of demo code are commented in the code itself. There are minor changes to some classes for <fieldset>
s and <button>
s. The structure is altered a little so keep that in mind. jQuery is versatile and it allows you to generalize DOM operations and do away with dependency on ids -- it's very possible to just use classes.
Events registered to dynamic tags fail unless you delegate events. To delegate a click event to all buttons existing currently and in the future, register an ancestor tag that the buttons commonly share (ex. #main
). Then assign the selectors of the buttons in the second parameter (event data):
$('#main').on('click', '.add, .remove', function(e) {...
As for removing a by clicking a nested button -- $(e.target)
and $(this)
can be used to reference the button that was currently clicked. When you need to find the appropriate ancestor of a clicked button (ex. .professional
) use .closest()
method like so:
$(e.target).closest('.professional').remove();
let index = 0;
// Hide the first .remove button
$('#remove').hide();
/*
Register the form to the click event
Event data directs .add and .remove buttons
*/
$("#main").on('click', '.add, .remove', function(e) {
// if the clicked button has .add
if ($(this).hasClass('add')) {
/*
clone the first .professional
increment counter
Reference all form controls of the clone
on each form control modify its id
*/
const dupe = $(".professional:first").clone(true, true);
index++;
const formControls = dupe.find('select, button, input, textarea');
formControls.each(function() {
let ID = $(this).attr('id');
$(this).attr('id', ID + index);
});
/*
Remove the legend from clone
Show the .add and .remove on clone
Hide the clicked button
Add clone to form
Stop event bubbling
*/
dupe.find('legend').remove();
dupe.find('.add, .remove').show();
$(e.target).hide();
$('#main').append(dupe);
e.stopPropagation();
// Otherwise if clicked button has .remove...
} else if ($(e.target).hasClass('remove')) {
/*
Find clicked button ancestor .professional and remove
it.
Hide all .add buttons
Show the last .add
Stop event bubbling
*/
$(e.target).closest('.professional').remove();
$('.add').hide();
$('.add:last').show();
e.stopPropagation();
} else {
// Otherwise just stop event bubbling
e.stopPropagation();
}
});
:root {
font: 400 14px/1 Consolas
}
fieldset {
width: fit-content
}
legend {
margin-bottom: -15px
}
label {
display: block
}
input,
select,
button {
display: inline-block;
font: inherit;
height: 3ex;
line-height: 3ex;
vertical-align: middle
}
.text input {
width: 24ch
}
select {
line-height: 4ex;
height: 4ex;
}
label b {
display: inline-block;
width: 7.5ch;
}
button {
position: absolute;
display: inline-block;
height: initial;
margin: 0;
}
.add {
position: absolute;
right: 0;
}
[for=level] b {
width: 6ch
}
.btn-grp {
position: relative;
width: 97%;
min-height: 26px;
padding: 0
}
<!DOCTYPE html>
<html>
<head></head>
<body>
<form action="" method="GET" id="main">
<fieldset class="professional">
<legend>
<h2>Professional Experience</h2>
</legend>
<label for="level">
<b>Nível: </b>
<select name="level" id="level">
<option value="empty">Selecione</option>
<option value="estagio">Estágio</option>
<option value="junior-trainee">
Junior/Trainee
</option>
<option value="aux-opera">
Auxiliar/Operacional
</option>
<option value="pleno">Pleno</option>
<option value="senior">Sênior</option>
<option value="sup-coord">
Supervisão/Coordenação
</option>
<option value="gerencia">
Gerência
</option>
</select>
</label>
<fieldset class='text'>
<label for="carrier"><b>Cargo: </b>
<input type="text" name="carrer" id="carrer">
</label>
<label for="company"><b>Empresa: </b>
<input type="text" name="company" id="company">
</label>
<label for="begin"><b>Início: </b>
<input type="month" name="begin" id="begin">
</label>
<label for="break"><b>Término: </b>
<input type="month" name="break" id="break">
</label>
</fieldset>
<label for="stl-work">
<input type="checkbox" name="stl-work" id="stl-work" >Ainda trabalho aqui
</label>
<label for="job-desc"><b>Descrição: </b></label>
<textarea name="job-desc" id="job-desc" placeholder="Conte um pouco sobre o que você fazia lá." cols="35" rows="1"></textarea>
<fieldset class='btn-grp'>
<button type="button" id='remove' class='remove'>Remove</button>
<button type="button" id='add' class="add">Add</button>
</fieldset>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
Upvotes: 1
Reputation: 206111
As suggested, Vue.js is cool, but jQuery has some forgotten powers too.
And, since you create elements dynamically, don't use IDs.
And submit to the backend your experiences as arrays []
: i.e: name="carrer[]"
, name="company[]"
etc. Than on the backend loop those data arrays to retrieve all the user experiences.
const new_exp = () => $('<div>', {
'class': 'professional-exp',
html: `
<label>Nível:
<select name="level[]">
<option value="empty">Selecione</option>
<option value="estagio">Estágio</option>
<!-- etc... -->
</select>
</label>
<label>Cargo: <input type="text" name="carrer[]"></label><br>
<label>Empresa: <input type="text" name="company[]"></label><br>
<label>Início: <input type="month" name="begin[]"></label>
<label>Término: <input type="month" name="break[]" ></label>
<label><input type="checkbox" name="stl-work[]"> Ainda trabalho aqui</label><br>
<label>Descrição: <textarea name="job-desc[]" placeholder="Conte um pouco sobre o que você fazia lá." cols="40" rows="1"></textarea></label><br>
`,
append: $('<button>', {
type: 'button',
text: 'Remove',
click() {
$(this).closest('.professional-exp').remove();
}
}),
appendTo: '#professional',
});
jQuery($ => { // DOM ready and $ alias in scope
new_exp(); // On init (Create first exp)
$("#new_exp").on('click', new_exp); // On click
});
.professional-exp {
padding: 10px;
margin-bottom: 10px;
background: #eee;
}
<form action="" method="POST" id="main">
<fieldset>
<legend>
<h2>Professional experience</h2>
</legend>
<div id="professional"></div>
<button type="button" id="new_exp">+ Add more</button>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Above we're defining the Remove's button action right within the template, but if you want you can also hardcode the button into the template and create a dynamic click handler (using jQuery's .on()
) like:
const exp_new = () => $('<div>', {
'class': 'professional-exp',
html: `
<label>Nível:
<select name="level[]">
<option value="empty">Selecione</option>
<option value="estagio">Estágio</option>
<!-- etc... -->
</select>
</label>
<label>Cargo: <input type="text" name="carrer[]"></label><br>
<label>Empresa: <input type="text" name="company[]"></label><br>
<label>Início: <input type="month" name="begin[]"></label>
<label>Término: <input type="month" name="break[]" ></label>
<label><input type="checkbox" name="stl-work[]"> Ainda trabalho aqui</label><br>
<label>Descrição: <textarea name="job-desc[]" placeholder="Conte um pouco sobre o que você fazia lá." cols="40" rows="1"></textarea></label><br>
<button class="exp_delete">REMOVE</button>
`,
appendTo: '#professional',
});
jQuery($ => { // DOM ready and $ alias in scope
exp_new(); // On init (Create first exp)
$("#exp_new").on('click', exp_new); // and on click.
$('#main').on('click', '.exp_delete', ev => $(ev.target).closest('.professional-exp').remove());
});
.professional-exp {
padding: 10px;
margin-bottom: 10px;
background: #eee;
}
<form action="" method="POST" id="main">
<fieldset>
<legend>
<h2>Professional experience</h2>
</legend>
<div id="professional"></div>
<button type="button" id="exp_new">+ Add more</button>
</fieldset>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Upvotes: 1
Reputation: 4226
One way to connect your new "remove" button with its "professional" div would be to add an extra statement in your event handler to update its id parallel to the new div's id, something like:
let lastIndex = index - 1;
$("#professional" + lastIndex).find("button").attr("id", "add-exp" + lastIndex);
(This code may not have the correct syntax -- I don't use jQuery very much -- but you can see the idea.)
A better way might be, when the "remove" button is clicked, don't remove according to ID, but instead find the closest ancestor div and remove that div.
For labels, you should leave out the id
s (because no two elements on the same page should ever have the same id
). And because the inputs are nested in the labels, you should be able to leave out the for
attribute as well and let the the association be implicit. (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label.)
Upvotes: -1
Reputation: 766
if you already wrap your input inside a label, you dont need id anymore,
and you can use this
as a parameter of delete button, so you can use it to delete your block.
Please check the following example
$(function(){
// keep the first block hidden as an empty template
$('.form-row:first').hide();
// trigger add new item
AddItem();
})
function AddItem(){
var container = $('#container');
// clone the form, show it & append before add button
var cloned = $('.form-row:first').clone().show().insertBefore($('#addBtn'));
}
function RemoveItem(elm){
// get form element & remove it
$(elm).closest('.form-row').remove()
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<style type="text/css">
.form-row {border:1px solid #ccc; margin:5px 0;padding:10px;}
</style>
<div id="container">
<div class="form-row">
<!-- wrap your input inside label tag to avoid using id as reference -->
<label>Field 1 : <input type="text" name="field1"></label>
<label>Field 2 : <input type="text" name="field2"></label>
<input type="button" value="Remove this item" onclick="RemoveItem(this)">
</div>
<input id="addBtn" type="button" value="Add new item" onclick="AddItem()">
</div>
Upvotes: 0