Reputation: 598
I have inventory form to submit inventories to database. I'm facing an issue in updating unit cost and total cost of dynamically generated rows. As you can see in snapshots below. The name of products are fetching via autocomplete jQuery.
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>Product Name/Code</th>
<th>Quantity</th>
<th>Unit Cost</th>
<th>Total Cost</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="p_scents">
<tr>
<td>1</td>
<td><input id="product_id" type="text" name="product_id[]" hidden><input id="product_code" type="text" name="product_code[]" hidden><input class="product_name form-control" id="product_name" type="text" placeholder="Type product name/code here" name="products[]" required></td>
<td><input class="quantity form-control" id="quantity" type="text" placeholder="Quantity to Buy" name="quantity[]" required /></td>
<td><div class="input-group"><span class="input-group-addon">$</span><input class="cost form-control" id="cost" placeholder="Unit Cost" type="text" readonly /></div></td>
<td><div class="input-group"><span class="input-group-addon">$</span><input class="total form-control" id="total" placeholder="Total" type="text" readonly /></div></td>
<td><button class="btn btn-default" type="button" id="addScnt"><i class="fa fa-plus "></i> Item</button></td>
</tr>
</tbody>
</table>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type='text/javascript'>
jQuery(document).ready(function(){
var scntDiv = $('#p_scents');
var i = $('#p_scents tr').size() + 1;
$('#addScnt').click(function() {
scntDiv.append('<tr>'+
'<td>'+i+'</td>'+
'<td><input id="product_id" type="text" name="product_id[]" hidden><input id="product_code" type="text" name="product_code[]" hidden><input class="product_name form-control" id="product_name" type="text" placeholder="Type product name/code here" name="products[]" required></td>'+
'<td><input class="quantity form-control" id="quantity" type="text" placeholder="Quantity to Buy" name="quantity[]" required /></td>'+
'<td><div class="input-group"><span class="input-group-addon">$</span><input class="cost form-control" id="cost" placeholder="Unit Cost" type="text" readonly /></div></td>'+
'<td><div class="input-group"><span class="input-group-addon">$</span><input class="total form-control" id="total" placeholder="Total" type="text" readonly /></div></td>'+
'<td><a id="remScnt" class="btn btn-danger btn-xs"><span title="Delete" class="glyphicon glyphicon-remove"></span></a></td>'+
'</tr>');
i++;
//return false;
$('.product_name').autocomplete({
source:'http://localhost/Multi-Channel_Shipping/inc/auto_product.php',
minLength:2,
select:function(evt, ui)
{
// when a product is selected, populate related fields in this form
this.form.cost.value = ui.item.cost;
this.form.product_id.value = ui.item.product_id;
this.form.product_code.value = ui.item.product_code;
}
});
$('.quantity').keyup(function() {
updateTotal();
});
$('.cost').keyup(function() {
updateTotal();
});
var updateTotal = function () {
var input1 = parseFloat($('.quantity').val());
var input2 = parseFloat($('.cost').val());
if (isNaN(input1) || isNaN(input2)) {
if(!input2){
$('.total').val($('.quantity').val());
}
if(!input1){
$('.total').val($('.cost').val());
}
} else {
$('.total').val(input1 * input2);
}
};
var output_total = $('.total');
var total = input1 + input2;
output_total.val(total);
});
//Remove button
$(document).on('click', '#remScnt', function() {
if (i > 2) {
$(this).closest('tr').remove();
i--;
}
return false;
});
$('.product_name').autocomplete({
source:'http://localhost/Multi-Channel_Shipping/inc/auto_product.php',
minLength:2,
select:function(evt, ui)
{
// when a zipcode is selected, populate related fields in this form
this.form.cost.value = ui.item.cost;
this.form.product_id.value = ui.item.product_id;
this.form.product_code.value = ui.item.product_code;
}
});
$('.quantity').keyup(function() {
updateTotal();
});
$('.cost').keyup(function() {
updateTotal();
});
var updateTotal = function () {
var input1 = parseFloat($('.quantity').val());
var input2 = parseFloat($('.cost').val());
if (isNaN(input1) || isNaN(input2)) {
if(!input2){
$('.total').val($('.quantity').val());
}
if(!input1){
$('.total').val($('.cost').val());
}
} else {
$('.total').val(input1 * input2);
}
};
var output_total = $('.total');
var total = input1 + input2;
output_total.val(total);
});
</script>
<?php
class DB
{
const DATABASE = 'multi-channel_shipping';
const HOST = 'localhost';
const USERNAME = 'root';
const PASSWORD = '';
static private $pdo;
static public function singleton()
{
if (!is_object(self::$pdo))
{
self::$pdo = new PDO('mysql:dbname=' . self::DATABASE . ';host=' . self::HOST,
self::USERNAME,
self::PASSWORD);
}
return self::$pdo;
}
private function __construct()
{
}
public function __clone()
{
throw new Exception('You may not clone the DB instance');
}
}
if (!isset($_REQUEST['term']))
{
die('([])');
}
$st = DB::singleton()
->prepare(
'SELECT * ' .
'FROM products ' .
'WHERE (name LIKE :name) OR (code LIKE :name) ' .
'ORDER BY name ASC ' .
'LIMIT 0,10');
$searchProduct = '%'.$_REQUEST['term'].'%';
$st->bindParam(':name', $searchProduct, PDO::PARAM_STR);
$data = array();
if ($st->execute())
{
while ($row = $st->fetch(PDO::FETCH_OBJ))
{
$data[] = array(
'value' => $row->code." - ".$row->name,
'cost' => $row->cost,
'product_id' => $row->id,
'product_code' => $row->code
);
}
}
echo json_encode($data);
flush();
?>
--
-- Table structure for table `products`
--
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(100) NOT NULL,
`name` varchar(255) NOT NULL,
`unit` varchar(50) DEFAULT NULL,
`cost` decimal(25,2) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `products`
--
INSERT INTO `products` (`id`, `code`, `name`, `unit`, `cost`) VALUES
(1, '4815162342', 'BAZIC 12 Dil Dil Pak', 'Packet', '0.10'),
(2, '23', 'Razer', 'Piece', '0.03');
I also need to put shipping cost input field and show grand total of invoice at bottom of table.
Upvotes: 1
Views: 1367
Reputation: 93571
There a multiple issues with the page & code, so I will try to cover what I can. @Barmar
also spotted additional issues so will try to cover everything and suggest some improvements.
JSFiddle: http://jsfiddle.net/TrueBlueAussie/vx15mr4n/29/
Templating:
Rather than use text strings in code, it is easier to maintain HTML as HTML. The example I have provided uses a dummy script block (of type="text/template", which will be ignored by all browsers) but you can access the HTML content with $('#template').html()
.
Duplicate ID are invalid
You can't have duplicate IDs in a page. That is invalid HTML and jQuery will only see the first match. Use classes on the added elements instead and match on those.
so use:
<a class="remScnt"
and
$(document).on('click', '.remScnt', function()
Note: you will need to sort out any other duplicate IDs too (like product_id
and quantity
and cost
and total
). Your code already uses classes for those, so just move/delete the id
attributes.
e.g. use classes for everything:
scntDiv.append('<tr>'+
'<td>'+i+'</td>'+
'<td><input class="product_id" type="text" name="product_id[]" hidden><input id="product_code" type="text" name="product_code[]" hidden><input class="product_name form-control" type="text" placeholder="Type product name/code here" name="products[]" required></td>'+
'<td><input class="quantity form-control" type="text" placeholder="Quantity to Buy" name="quantity[]" required /></td>'+
'<td><div class="input-group"><span class="input-group-addon">$</span><input class="cost form-control" placeholder="Unit Cost" type="text" readonly /></div></td>'+
'<td><div class="input-group"><span class="input-group-addon">$</span><input class="total form-control" placeholder="Total" type="text" readonly /></div></td>'+
'<td><a class="remScnt btn btn-danger btn-xs"><span title="Delete" class="glyphicon glyphicon-remove"></span></a></td>'+
'</tr>');
You are using a delegated event for one handler but not the others. You also need to add them for keyup (which can be combined as the code is the same):
$('#p_scents').on('keyup', '.quantity .cost', function() {
updateTotal();
});
IMPORTANT: Your code here is not matching a specific row. Also use @Barmar's
fix like this to pass the current row:
$('#p_scents').on('keyup', '.quantity .cost', function() {
updateTotal($(this).closest('tr'));
});
Update: As Regent
mentions below, you should not use document
but use #p_scents
for your delegated event handler:
$('#p_scents').on('click', '.remScnt', function()
A delegated event should be attached to the closest non-changing ancestor (if one is convenient/available). This will give a very small speed increase as it stops lower in the DOM.
I also cleaned up the event handler doing the calculations which now using temp vars, for elements relative to the row, and looks like:
// Update the row total of a specific row
var updateTotal = function ($row) {
// Get the specific inputs
var $quantity = $('.quantity', $row);
var $cost = $('.cost', $row);
var $total = $('.total', $row);
var input1 = parseFloat($quantity.val());
var input2 = parseFloat($cost.val());
if (isNaN(input1) || isNaN(input2)) {
if (!input2) {
$total.val($quantity.val());
}
if (!input1) {
$total.val($cost.val());
}
} else {
$total.val(input1 * input2);
}
var total = input1 * input2;
$total.val(total);
};
Note: Without the missing data, I cannot easily test the code, but you should get the idea.
Grand total
To update the grand total, you need to iterate all .total
fields and add them to the shipping cost:
var updateGrandTotal = function()
{
// Now update the grand total
var grandTotal = 0;
$('.total').each(function () {
grandTotal += parseFloat($(this).val());
});
var shipping = parseFloat($('.shippingcost').val());
$('.grandtotal').val(grandTotal + shipping);
}
As you will want to update the grand total when the shipping changes, I refactored it out so it could also be called from a keyup on the shipping:
$('.shippingcost').keyup(function(){
updateGrandTotal();
});
The other issue is the autocomplete (which I could not test without a real data feed):
Basically get the select event to refer to the current field's row and find the appropriate fields to update:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/vx15mr4n/23/
select: function (evt, ui) {
// when a product is selected, populate related fields in this form
var $tr = $(this).closest("tr");
$(".cost",$tr).val(ui.item.cost);
$(".product_id", $tr).val(ui.item.product_id);
$(".product_code", $tr).val(ui.item.product_code);
}
Upvotes: 3
Reputation: 781058
When updateTotal()
uses $('.quantity').val()
it gets the value of the first field with that class, not the one in the row that the user was typing in. You need to pass the row to the function. Also, since the elements are added dynamically, you need to use delegation for the event bindings.
$('#p_scents').on('keyup', '.quantity, .cost', function() {
updateTotal($(this).closest('tr'));
});
var updateTotal = function (row) {
var input1 = parseFloat($('.quantity', row).val());
var input2 = parseFloat($('.cost', row).val());
if (isNaN(input1) || isNaN(input2)) {
if(!input2){
$('.total', row).val(input1);
}
if(!input1){
$('.total', row).val($(input2);
}
} else {
$('.total', row).val(input1 * input2);
}
}
var output_total = $('.total', row);
var total = input1 + input2;
output_total.val(total);
};
Upvotes: 0