I've build an interactive table where you can manually add and delete rows (with bootstrap). This all works fine!
Now comes the problem: I have some input fields with an automatic calculation. But the problem is, the table only performs the calculation in the standard row, and not in the addable row.
This is the .HTML and .JS of the table:
jQuery(document).delegate('a.add-record_venr', 'click', function(e) {
e.preventDefault();
var content = jQuery('#sample_table_venr tr'),
size = jQuery('#tbl_posts_venr >tbody >tr').length 1,
element = null,
element = content.clone();
element.attr('id', 'rec_venr-' size);
element.find('.delete-record_venr').attr('data-id', size);
element.appendTo('#tbl_posts_body_venr');
element.find('.sn').html(size);
});
jQuery(document).delegate('a.delete-record_venr', 'click', function(e) {
e.preventDefault();
var didConfirm = confirm("Ventilatierooster verwijderen?");
if (didConfirm == true) {
var id = jQuery(this).attr('data-id');
var targetDiv = jQuery(this).attr('targetDiv');
jQuery('#rec_venr-' id).remove();
//regnerate index number on table
$('#tbl_posts_body_venr tr').each(function(index) {
//alert(index);
$(this).find('span.sn').html(index 1);
});
return true;
} else {
return false;
}
});
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<div >
<a data-added="0"><i ></i> Toevoegen </a>
</div>
<div style="margin-left:1px ;">
<table id="tbl_posts_venr">
<thead style="background-color:#c7c8cc; width: 100% !important;">
<tr>
<th style="width:30px ;">Merk</th>
<th >Lengte rooster</th>
<th >Type rooster</th>
<th >Capaciteit [dm3/s]</th>
<th >Max. capaciteit [dm3/s]</th>
<th >Verblijfsgebied</th>
<th >Verblijfsruimte</th>
<th >Verwijderen</th>
</tr>
</thead>
<tbody id="tbl_posts_body_venr">
<tr id="rec_venr-1">
<td><input type="text" name="" placeholder="Merk"> </td>
<td><input type="text" name="lrooster" id="lrooster" placeholder="Lengte rooster"></td>
<td><input type="text" name="" placeholder="Type rooster"></td>
<td><input type="text" name="caprooster" id="caprooster" placeholder="Capaciteit [dm3/s]"></td>
<td><input type="text" name="maxcaprooster" id="maxcaprooster" placeholder="Max. capaciteit [dm3/s]"></td>
<td><input type="text" name="" placeholder="Verblijfsgebied"></td>
<td><input type="text" name="" placeholder="Verblijfsruimte"></td>
<td><a data-id="1"><i ></i></a></td>
</tr>
</tbody>
</table>
</div>
<div style="display:none;">
<table id="sample_table_venr">
<tr id="">
<td><input type="text" name="" placeholder="Merk"> </td>
<td><input type="text" name="lrooster" id="lrooster" placeholder="Lengte rooster"></td>
<td><input type="text" name="" placeholder="Type rooster"></td>
<td><input type="text" name="caprooster" id="caprooster" placeholder="Capaciteit [dm3/s]"></td>
<td><input type="text" name="maxcaprooster" id="maxcaprooster" placeholder="Max. capaciteit [dm3/s]"></td>
<td><input type="text" name="" placeholder="Verblijfsgebied"></td>
<td><input type="text" name="" placeholder="Verblijfsruimte"></td>
<td><a data-id="1"><i ></i></a></td>
</tr>
</table>
</div>
</div>
</div>
</table>
This is the JavaScript of the calculation:
$('#lrooster, #caprooster').keyup(function(){
var lrooster = parseFloat($('#lrooster').val());
var caprooster = parseFloat($('#caprooster').val());
$('#maxcaprooster').val(lrooster * caprooster );
});
If the linked code doesnt work good, here 's a fiddle
thanks in advance!!
CodePudding user response:
The main issue in your code is because you're using id
attributes in the HTML of each row which you clone. This causes duplicates which is invalid - id
attribute values must be unique within the DOM. To fix this use common class
attributes instead, to group elements by behaviour.
That said, there's also several other issues in your code which need to be addressed:
- Don't generate incremental
id
attributes at runtime. It's an anti-pattern which causes more bugs, additional complexity and makes the code more difficult to maintain. User common classes and DOM traversal methods to relate elements to each other as required. - The version of jQuery you're using is outdated. Update to 3.6.0
- Bootstrap is outdated too, however I'll leave this as an exercise for the OP to update as there may be some breaking changes in moving to v5.
- Don't use inline styling on elements - use a stylesheet.
delegate()
was deprecated from jQuery a long time ago. Useon()
instead.- Use the
input
event instead ofkeyup
. The latter does not listen for content added to the form control using the mouse, the former does. - Use the
$
variable, where available to make your code more succinct. You can alias it through the document.ready handler if necessary. - Use a
<template />
element to store HTML which will be used to create dynamic content at runtime. - Give all form controls a name, even if you're submitting the form via AJAX.
- Use
data()
to accessdata
attributes, notattr()
. - Create delegated event handlers for all dynamic content (ie. the
lrooster
andcaprooster
inputs)
With all those issues corrected, here's a working example:
jQuery($ => {
let rowHtml = $('#sample_table_venr').html();
let appendRow = () => $('#tbl_posts_body_venr').append(rowHtml);
appendRow(); // add a row on page load
$(document).on('click', 'a.add-record_venr', e => {
e.preventDefault();
appendRow(); // add a row on button click
});
$(document).on('click', 'a.delete-record', e => {
e.preventDefault();
if (confirm("Ventilatierooster verwijderen?")) {
$(e.target).closest('tr').remove();
}
});
$(document).on('input', '.lrooster, .caprooster', e => {
let $row = $(e.target).closest('tr');
let lrooster = parseFloat($row.find('.lrooster').val()) || 0;
let caprooster = parseFloat($row.find('.caprooster').val()) || 0;
$row.find('.maxcaprooster').val(lrooster * caprooster);
});
});
.container-fluid {
margin-left: 1px;
}
#tbl_posts_venr thead {
background-color: #c7c8cc;
width: 100% !important;
}
#tbl_posts_venr thead th:first-child {
width: 30px;
}
/* only for this demo, to help the content fit in the snippet preview */
input {
width: 75px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<div >
<a data-added="0">
<i ></i> Toevoegen
</a>
</div>
<div >
<table id="tbl_posts_venr">
<thead>
<tr>
<th>Merk</th>
<th>Lengte rooster</th>
<th>Type rooster</th>
<th>Capaciteit [dm3/s]</th>
<th>Max. capaciteit [dm3/s]</th>
<th>Verblijfsgebied</th>
<th>Verblijfsruimte</th>
<th>Verwijderen</th>
</tr>
</thead>
<tbody id="tbl_posts_body_venr"></tbody>
</table>
</div>
<template id="sample_table_venr">
<tr>
<td><input type="text" name="merk" placeholder="Merk"> </td>
<td><input type="text" name="lrooster" placeholder="Lengte rooster"></td>
<td><input type="text" name="typerooster" placeholder="Type rooster"></td>
<td><input type="text" name="caprooster" placeholder="Capaciteit [dm3/s]"></td>
<td><input type="text" name="maxcaprooster" placeholder="Max. capaciteit [dm3/s]"></td>
<td><input type="text" name="verblijfsgebied" placeholder="Verblijfsgebied"></td>
<td><input type="text" name="verblijfsruimte" placeholder="Verblijfsruimte"></td>
<td>
<a data-id="1">
<i ></i>
</a>
</td>
</tr>
</template>
CodePudding user response:
Problem
#id
s Must be Unique, Duplicate #id
s Invalidates HTML
Never clone (or replicate) any element with an #id
. In the OP (Original Post), there are 3 #id
s being duplicated each time a row is added, which are #lrooster
, #caprooster
, and #maxcaprooster
. When the browser is directed to find an #id
, it naturally assume there's only one #id
to find and it stops once the first one is found. That is why the first row is the only one that works.
Solution
Avoid using #id
and use .class
instead. This is especially important to remember if you are using jQuery because of it's versatility. In fact, you are more than likely to hinder the functionality of your code should you use #id
so generously.
Changes
Upgrades
- jQuery 3.6.0 Slim
- Bootstrap 5.1.3 CSS and JavaScript
- Bootstrap Icons 1.8.3 CSS
Replacements
.delegate()
is now.on()
#id
s are now.class
es#lrooster
,#caprooster
, and#maxcaprooster
are now.lrooster
,.kaprooser
, and.maxkaprooster
and each aretype="number"
confirm()
is now a Bootstrap modal-dialog- Use of
data-*
to determine index position for serializing#id
s is a lot of bookkeeping, so it is now a single.active
class assigned to a<tr>
. - BS classes are assigned throughout the HTML.
Details are commented in example
// Hide button.remove of the first row
$('.remove').hide();
// Click button.add...
$('.add').on('click', function(e) {
// Clone the first row of tbody
let copy = $('tbody tr:first').clone(true, true);
// Clear all of it's inputs
copy.find('input').val('');
// Reset number inputs to 0
copy.find('[type="number"]').val('0');
// Show button.remove
copy.find('.remove').show()
// Add clone to tbody
$('tbody').append(copy);
});
// Click button.remove...
$('.remove').on('click', function(e) {
// Add .active to the <tr> that contains clicked button
$(this).closest('tr').addClass('active');
});
// Click button.confirm...
$('.confirm').on('click', function(e) {
// Remove tr.active
$('.active').remove();
});
// Click button.close...
$('.close').on('click', function(e) {
// Remove .active from <tr>
$('tr').removeClass('active');
});
// Enter data in any number input...
$('[type="number"]').on('input', function() {
/*
Reference <tr> that contains input that the user is currently
interacting with
*/
const $row = $(this).closest('tr');
// Convert the values of .lrooster and .kaprooster of current row
let L = parseFloat($row.find('.lrooster').val());
let K = parseFloat($row.find('.kaprooster').val());
/*
If the product of .lrooster and .kaprooster equals NaN, retrun 0
otherwise the product
*/
let rooster = NaN ? 0 : L * K;
// Display result in .maxkaprooster
$row.find('.maxkaprooster').val(rooster);
});
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" rel="stylesheet" />
<main >
<section class='row'>
<menu class='d-flex flex-row-reverse mt-1 mb-1'>
<button style='width: max-content;'><i ></i> Toevoegen </button>
</menu>
<table >
<thead class='table table-dark'>
<tr>
<th>Brand</th>
<th>Grille Length</th>
<th>Grille Type</th>
<th>Capacity [dm³/s]</th>
<th>Max. [dm³/s]</th>
<th>Home</th>
<th>Room</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text"></td>
<td><input name="lrooster" type="number" value='0'></td>
<td><input type="text"></td>
<td><input name="kaprooster" type="number" value='0'></td>
<td><input name="maxkaprooster" type="number" readonly value='0'></td>
<td><input type="text"></td>
<td><input type="text"></td>
<td><button data-bs-toggle="modal" data-bs-target="#modal"><i ></i></button></td>
</tr>
</tbody>
</table>
</section>
<aside id='modal' tabindex="-1">
<dialog open>
<article >
<header >
<h5 >Confirm Row Removal</h5>
<button data-bs-dismiss="modal">X</button>
</header>
<section >
<p>Ventilatierooster verwijderen?</p>
</section>
<footer >
<button data-bs-dismiss="modal">Cancel</button>
<button data-bs-dismiss="modal">Remove Row</button>
</footer>
</article>
</dialog>
</aside>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>