Jquery sum of inputs from table - javascript

Im trying to get the sum of an input from a table. Currently I have:
HTML:
<tr id='saved1'>
<td><input class='qty'/></td>
<td><input class='price'/></td>
<td><input class='subtotal'/></td>
</tr>
Jquery:
//COUNT ALL FIELDS
function calculateSum() {
$("[id^=saved]").each(function() {
sum = $(this).parent().parent().find('.qty').val() * $(this).parent().parent().find('.price').val();
$(this).parent().parent().find('.subtotal').val(sum);
});
}
// On change
$("input").change(calculateSum);
Any Ideas?

Your DOM traversal is wrong. You don't need to go up to the parents, you just need to find the elements in that row.
function calculateSum() {
$("[id^=saved]").each(function() {
var sum = $(this).find('.qty').val() * $(this).find('.price').val();
if (!isNaN(sum)) {
$(this).find('.subtotal').val(sum);
}
});
}
If the rows are being added dynamically, you need to use event delegation for the binding:
$("#tableID").on('change', 'input', calculateSum);
DEMO

Okay, so your code has a variety of problems (I believe the transversal was wrong, and you are multiplying rather than adding, and you aren't checking for not a numbers/NaNs), and I'm going to address as many as I can.
The cascading style of organization was very hard to read. Using variables can be much easier to read and debug. As you can see, I declared multiple variables with one var declaration. Use var. It makes variables local rather than global, so you can define sum somewhere else without messing up your original functions.
You also try adding 2 unchecked inputs together. As you can see I'm using the bitwise operator:
qty = grandparent.find('.qty').val() | 0,
This returns 0 if qty isn't an integer. This is important, otherwise it will assume the inputs are strings, and append one, rather than adding them together.
I would also strongly recommend you avoid tags such as $("[id^=saved]"). Selecting by attributes can be very costly/slow. Not something to worry about immediately, but you see how I avoided using that selector below. You generally want to use an id, class, tag or variable as your selector.
function calculateSum() {
var $this = $(this),
grandparent = $(this).parent().parent(),
qty = grandparent.find('.qty').val() | 0,
price = grandparent.find('.price').val() | 0,
sum = qty + price;
$('.subtotal').val(sum);
}
// On change
$("input").change(calculateSum);
Working fiddle

JSBIN
function foo(){
foo.total += $(this).val() - 0;
}
function calcField(selector){
foo.total = 0;
$(selector).each(foo)
$(selector+'total').val(foo.total);
}
function calcFields(){
var fields = [".qty",".price",".subtotal"];
for(var i in fields){
calcField(fields[i]);
}
}
$('button').on('click',calcFields);

Related

JS: I can't get the inner HTML of elements with a specific class name

I'm trying to create a calculator out of javascript to work on my skills. I've added the class num to all of my buttons that have a number.
I'm trying to display to display the innerHTML of those buttons in the console when I click them with this code:
var num = document.getElementsByClassName('num');
num.addEventListener('click', getNum);
function getNum(){
console.log(num.innerHTML);
}
getNum();
However all I get is
num.addEventListener is not a function.
Here is my codepen: https://codepen.io/teenicarus/pen/wrEzwd
what could I be doing wrong?
You need to change the code like below. getElementsByClassName returns collection of elements. Loop through the elements and add click event listener. In getNum, you can use this to get access to the button clicked.
var num = document.getElementsByClassName('num');
for (var i = 0; i < num.length; i++) {
num[i].addEventListener('click', getNum);
}
function getNum(){
console.log(this.innerHTML);
}
You can also use Array forEach like the following:
[].forEach.call(num, function(el){
el.addEventListener('click', getNum);
})
getElementsByClassName returns a collection of elements, not a single element. If you want to get single element assign it an id attribute and use getElementById. This way you can use addEventListener function
Here's a solution you can plug directly in your codepen:
var nums = document.getElementsByClassName('num');
[].forEach.call(nums, num => num.addEventListener('click', numClick));
function numClick(){
// adding + turns the text into an actual number
console.log(+this.innerHTML);
}
getElementsByClassName() returns an HTMLCollection, to iterate over it you can pass it to [].forEach.call() like I showed above.
I also renamed the handler to numClick, since it doesn't "get" the number. And added +, which is a nice shortcut to turn text into a number (otherwise, adding two numbers would yield unexpected results, like "1" + "2" => "12"
The .getElementsByClassName returns not an element, but a collection of them.
You can access elements using .getElementsByClassName(num)[element's sequential number], or better use id's and getElementById method.
Here is the modified code for your desired output.just copy and try:
var num = document.getElementsByClassName('num');
//num.addEventListener('click', getNum);
for (var i = 0; i < num.length; i++) {
num[i].addEventListener('click', getNum);
}
function getNum(){
document.getElementById('result').innerHTML+=this.innerHTML;
console.log('value:'+this.innerHTML);
}
//getNum();
As you tagged Jquery to your question I suppose that you are able to use Jquery as well. You can grab the clicked element's class and referance it with 'this' to get its text.
$('.num').click(function(){
var x = $(this).text();
console.log(x);
});
This is a working example you can check the console.log DEMO

Issue on calling Javascript function twice results called once

I am working on a project that requires a form be built. The form has a function that sums up the columns as well as the rows. I am strictly using HTML and JavaScript. I am unable to get the JavaScript function called twice, once for the row and once for the column (I will actually be calling it 3 times as I need to do section totals as well). I have created different classes for the column controls that will need summed up and a different class for the row controls that will need to be summed up, hence the two different classes in the input control. I also believe that it could be in the for loop as I commented it out and put used an alert statement and it seemed to work perfectly. See the following code:
JavaScript:
<script type="text/JavaScript">
function CalcSum(displayIn, calcClass){
var sum = 0;
var displayCell = displayIn;
className = calcClass;
var divs = document.getElementsByClassName(className);
var args = [];
for (var i = 0; i <=divs.length; i++){
args.push(divs[i].value);
val = divs[i].value;
sum += val*1;
document.getElementById(displayCell).value = sum;
dollarAmount("Form1", displayCell);
}
}
HTML Control:
<input type="text" name="ctl_001" value="" id="ctl_001" class="col4txrev col4" onchange="CalcSum('T1_TOT_C4_TXREV','col4txrev');CalcSum('T1_TOT_C4','col4');" style= "width: 100%">
You have multiple errors in your script technically and functionally based on my understanding of your question.
I have corrected the errors and can see the console printing the log twice when they called.
Note: Anyways, don't call the function twice from the inline attribute. Create another function which will do the same and call it from the onchange event (or) create the onchange listener programmatically.
When looping the elements, condition should be i < divs.length and
not i <= divs.length
To find a text inside the div, it should be innerHTML as below
and not value. value can be used for the form input elements
which values can be changed by the end users.
To calculate the sum, the value should be converted to a number
using either parseInt or parseFloat since the text/value of
the element is generally a text.
If you have to assign the final value of the sum to another div
element and call another method, it should be outside the for
loop. But if you really need this to set/call for each loop, then it
can be inside the for loop.
function CalcSum(displayIn, calcClass){
var sum = 0;
var displayCell = displayIn;
var className = calcClass;
console.log('called');
var divs = document.getElementsByClassName(className);
var args = [];
for (var i = 0; i < divs.length; i++){
//args.push(divs[i].value);
var val = divs[i].innerHTML;
args.push(val);
sum += parseInt(val) * 1; // It can be parseFloat
}
document.getElementById(displayCell).value = sum;
dollarAmount("Form1", displayCell);
}
// dummy function
function dollarAmount(form, elm){
}
<input type="text" name="ctl_001" value="" id="ctl_001" class="col4txrev col4" onchange="CalcSum('T1_TOT_C4_TXREV','col4txrev');CalcSum('T1_TOT_C4','col4');" style= "width: 100%">
<div class="col4txrev">10</div>
<div id="T1_TOT_C4_TXREV"></div>
<div class="col4">20</div>
<div id="T1_TOT_C4"></div>

Simplifying a javascript function with repeated similar lines (with a loop?)

Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.

Updating a span using jQuery, based on a simple live calculation from a input field

So I have a tiny issue with some jQuery that I would appriciate some help on. I have two values, the one I get from an input field, the other from a static element. So it should be fairly simple, and I would like it as "LIVE" as possible. I made a JSFIDDLE for the sake of it. Please check it out.
http://jsfiddle.net/pzSxL/2/
$('#bet-amount').keyup(function() {
var amount = $("#bet-amount").val();
var odds = 2;
var total = amount * odds;
$("#potential-payout").val(total);
});
Any help is greatly appriciated, and I hope that you can see that what i want is to update the span containing a 0 as default.
Best regards,
André!
You made mistake with span.val(); You should use html or text jQuery method to update html inside span, and another thing, you should parse value from input field. So this is your function.
$('#bet-amount').keyup(function() {
var amount = parseFloat($("#bet-amount").val());
var odds = 2;
var total = amount * odds;
$("#potential-payout").html(total); // sets the total price input to the quantity * price
});
Try with this: http://jsfiddle.net/pzSxL/3/
var total = amount * odds;
$("#potential-payout").text(total);
// ------------^^^^^-----------you had to change this.
You cannot use the val function to change inner html. Html is the correct function:
$("#potential-payout").html(total);
$('#bet-amount').on('keyup', function() {
var amount = parseFloat( this.value ),
odds = 2,
total = amount * odds;
$("#potential-payout").text(total);
});
FIDDLE
The returned result from value is always a string, so a little parsing should be done, and when setting the "value" of the span, that is actually not the value, but the text content, and text() would be more appropriate.

Sorting Divs in jQuery by Custom Sort Order

I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;

Categories

Resources