jQuery Calculation Plug-in: Making calculating easy...
I actually wrote this plug-in months ago, but only made passing mention about in on the jQuery mailing list. Since the topic of dynamic calculation came up again today on the mailing list, I thought I'd go ahead and officially announce the plug-in here.
My jQuery Calculation Plug-in was designed as a generic mathematical library to make it easy to do things like sum or average values that are displayed on the page. For example, to get the sum of all of the elements with the class of "price" you'd use:
Since I did not want to restrict calculation to only form elements, the plug-in has helper method called parseNumber() which is called internally which parses a numeric value from an element. It can parse both form elements and normal HTML elements (like <div> or <span> elements.) In order to allow parsing of HTML elements that might contain additional formatting in the element, I'm using a regular expression of /\d+(,\d{3})*(\.\d{1,})?/g to parse the contents of the element to find the first thing that looks like a number. And don't worry, you can configure the regular expression if it doesn't meet your needs.
This plug-in actually gives you a lot of power. For example, let's say we have a form that looks like:
Wouldn't it be great if you could update all the calculations on the screen without writing a ton of code? Well, this is where the Calculation Plug-in really shines. The HTML for the table above is:
<col style="width: 50px;" />
<col />
<col style="width: 60px;" />
<col style="width: 110px;" />
<tr>
<th>
Qty
</th>
<th align="left">
Product
</th>
<th>
Price
</th>
<th>
Total
</th>
</tr>
<tr>
<td align="center">
<input type="text" name="qty_item_1" id="qty_item_1" value="1" size="2" />
</td>
<td>
<a href="http://www.packtpub.com/jQuery/book">Learning jQuery</a>
</td>
<td align="center" id="price_item_1">
$39.99
</td>
<td align="center" id="total_item_1">
$39.99
</td>
</tr>
<tr>
<td align="center">
<input type="text" name="qty_item_2" id="qty_item_2" value="1" size="2" />
</td>
<td>
<a href="http://jquery.com/">jQuery Donation</a>
</td>
<td align="center" id="price_item_2">
$14.99
</td>
<td align="center" id="total_item_2">
$14.99
</td>
</tr>
<tr>
<td colspan="3" align="right">
<strong>Grand Total:</strong>
</td>
<td align="center" id="grandTotal">
</td>
</tr>
</table>
In order to hook our form up so that all of the totals are automatically calculated on the fly, we just need to bind a function to the quantity fields to be triggered on each key press:
$("input[@name^=qty_item_]").bind("keyup", recalc);
Our recalc() function looks like this:
// run the calc() method on each of the "total" fields
$("[@id^=total_item]").calc(
// the equation to use for the calculation
"qty * price",
// we now define the values for the variables defined in the equation above
{
// instead of using a static value, we use a jQuery object which grabs all the quantities
qty: $("input[@name^=qty_item_]"),
// now we define the jQuery object which reads in the "price" from the table cell
price: $("[@id^=price_item_]")
},
// this function is execute after the calculation is completed, which allows us to
// add formatting to our value
function (s){
// return the number as a dollar amount
return "$" + s.toFixed(2);
},
// once all calculations are completed, we execute the code below
function ($this){
// now we get the sum() of all the values we just calculated
var sum = $this.sum();
// now that we have the grand total, we must update the screen
$("#grandTotal").text(
// round the results to 2 digits
"$" + sum.toFixed(2)
);
}
);
}
Well this code may look a little overwhelming at first, it's actually very easy to implement. You can see this example code in action on the Calculation Plug-in's home page.
The one big "gotcha" with using jQuery selectors as variables for the equations is that your selectors must return arrays of equal size. The calc() engine needs matching arrays, so that it can correct apply the calculations to each position in the array. For example, the above code actually translates to:
qty[0] * price[0] = total[0]
qty[1] * price[1] = total[1]
I think for the most part this should be a big deal, but I guess if you had a really strange layout trying to get your selectors to match up array items correctly could be programmatic. Unfortunately, I'm not sure there's a solution to that problem that doesn't involve a lot of complexity.
Run example code
Comments
I just checked this in IE7 and it's working fine for me. Is there something specific that's not working for you?
if tested with your demo link and nothing (calculations) it´s working, but it works fine on FF.
regards,
I tested the code on my example page under IE7 and everything worked as expected. Perhaps you have something filtering/blocking content that's running under IE7.
What errors are you seeing? I have tested the code under two different IE7 installations and the page works on both machines. It also works under IE6 for me.
...and what might be filtering/blocking ?
i don´t have any firewall installed and i´ve stopped my anti-virus also.
One culprit might be my check to see if Firefox is installed:
bIsFirebugReady = (!!window.console && !!window.console.log);
In normal circumstances, IE7 should not return true for that line--but perhaps you have something installed (like a debugger) which is causing the bIsFirebugReady check to be true.
I'll certainly look into any errors you post for me.
Whatever info you can give me, I'll definitely try to fix the problem. It's just hard to address when you can't replicate and without more information on the error(s) you're getting, there's not a whole lot I can do. :)
Thanks for the great plug-in. One question/feature request: It does not handle negative numbers. I think I was able to overcome this by changing the regex to allow an minus sign. Do you think this will suffice?
reNumbers: /-?\d+(,\d{3})*(\.\d{1,})?/g
I suspect that is why you have the regex as an option. My point in writing was merely to ask if my regex would work okay and allow negative numbers; and, if so, to suggest that you may want to make this the default. For example, on your demo page, if you enter a negative number, it actually sums or averages the absolute of it, which is a little misleading.
Thanks again for the great plug-in!
Dave
That's definitely one of the reasons I saw the need to make the regex configurable. Plus, European number formatting is different from the US.
Adding support for negative numbers was definitely an oversight on my part. The regex you have should work fine.
As soon as I get a chance to do some more testing, I'll add the check for negative numbers to the next revision.
I think the work I did last week to the sum() and avg() methods make it much easier to use. I'm always open to other suggestions as well.
I updated the code this weekend and added support for negative numbers. Please test the new build and let me know how it works. I also added some callback methods for handling when the parseNumber() method can't find a numeric value in the element.
Thanks for the nice plug-in.
How to calculate the time can u pl give an idea
Thanks
Venkat
:)
You can use whatever event you want to trigger the re-running of the $.calc() method--my example just uses the keyup. You could use my example pretty much verbatim, but just change the "keyup" event to the event you want to use (for a select box, you'll whant "change".)
I've just started using your script, I like it a lot. But I'm working on a system which also needs to give a discount after a certain quantity number. I've been struggling with it for a while now, but I just can't get it right, could you help me with this?
Thanks!
What's your code look like, is there an example I can see? Also, what problem are you having?
This isn't a jQuery problem, but an IE problem with form fields which have a name attribute value of "id" or "name".
http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
Great plugin, it's really helped me a lot. Do you have any examples of how to work out the difference between two groups of fields? I know it should be simple but I'm stuck.
e.g. I have a group with the class ".revenue" and one with ".cost" so I need to get the sum() of each and find the difference and put it in a 'total' field.
cheers
t
Take a look at the example of the shopping cart and look at the code for the calc() method. It shows off similar functionality.
Thanks, hal
You should do the calculations on the server side. Never trust data coming from the client.
I have one question though. I'd like to format the totals (i want to group the digits).
At the moment I'm using this:
$("input[@name$=_x]").sum("keyup", "#som_month_x");
I tried to convert this to something similar to the calc() example above, so that I'd
have a cbFormat argument.
What would be the best way to realize this? Thanks !!!
You can pass in an "oncalc" option, which is a call back that gets triggered after the calculation is performed. You can use this to format the value.
$("input[@name^=input_]").sum({bind:"keyup",oncalc:myOnCalc}, "#som");
Thanks !
function formatTotals(s, opt) { return s.toFixed(2); // test }
If you look at the max() example in the demo, you could change that to something like:
$("input[@name^=max]").max("keyup", {
selector: "#numberMax"
, oncalc: function (value, options){
// you can use this to format the value
$(options.selector).val("+" + value);
}
});
This would add a plus symbol in front of the max value. If you reference "this" from inside the oncalc callback, it'll return a reference to the jQuery object of $("input[@name^=max]").
One more question: I need to support the German number format for decimals where the "," is used as decimal point. Although I can change the regex for parsing the number format, I need to convert the comma into a dot before I can do any calculations. I can't figure out where to do the "replace()" without changing your plugin code. For the moment I changed your "parseNumber()" function by replacing this line
v = v[0].replace(/[^0-9.\-]/g, "");
with this line
v = v[0].replace(/,/g, ".").replace(/[^0-9.\-]/g, "");
I'm sure there's a better solution without the need to change your code.
Thanks for you help!
Yeah, there's not a good way to handle that right now. What I would suggest is add an onParseNumber callback instead. You could then use that to convert the European format to US for the actual math operations.
It would go something like this...
Add this line above the onParseError in the defaults:
// a callback function to run once when parsing a number
, onParseNumber: null
Now change the lines:
// clense the number one more time to remove extra data (like commas and dollar signs)
v = v[0].replace(/[^0-9.\-]/g, "");
To:
// get the number
v = (jQuery.isFunction(options.onParseNumber)) ? options.onParseNumber.apply($el, [v[0]]) : v[0];
// cleanse the number one more time to remove extra data (like commas and dollar signs)
v = v.replace(/[^0-9.\-]/g, "");
What you can do now is define the onParseNumber callback and have it return the value formatted with commas converted to periods.
Add this to your source code (not the jquery.calculation.js code):
$.Calculation.setDefaults({
onParseNumber: function(value){
return value.replace(/,/g, ".");
}
});
I haven't test this, but it should work. It also gives you more flexibility to do other things in the future.
How would I get something like this to work?
qty * price + fee
I'd love to hear what you guys have to say, because I can't get it to work.
You should never trust values sent from the client to the server--it's too easy to manipulate the values. When calculating values in the browser like this, it should be done simply to improve the UI. You should not be trusting this values, but should always verify data and do calculations on the server-side.
I want to use the grandTotal as a value of an input area. I've tried everything I can but can't work. Any idea :(
There shouldn't be an issues using an input field for value--as the examples show. I'd make sure you're using the most recent version:
http://www.pengoworks.com/workshop/jquery/calculat...
Also, while there shouldn't be any issues with using an input field, I'd advise against it, because there's no general reason to use. You never want to trust a value coming from the client like that--you'd want to do the calculations on the server anyway.
As I said, there should be no problem using an input element to update the value to. However, I would *never* be posting calculated values to a merchant that were generated via JavaScript. This makes your site extremely vulnerable to hacking. I hope you're not in any way trusting these values to be accurate.
Thx for quick answer. But client side security is not a problem in this project.
This is your(my) script part for grandTotal:
$("[id^=grandTotal]").text(
// round the results to 2 digits
sum.toFixed(2) + "TL"
);
and this the output:
<td (or div) id="grandTotal">'grandTotal value'</td (or /div)>
but I want this output:
<input type="text" id="grandTotal" value="'grandTotal value'" name="whatever">
or
<td (or div) id="grandTotal">'grandTotal value'</td (or /div)>
<input type="hidden" name="whatever" value="'grandTotal value'">
Thanks a lot.
If your trying to update the value of an input element, you want to use val() instead of text()--that's your problem. What I'd recommend doing is something like this instead:
$("input[name^='sum']").sum({
bind: "keyup"
, selector: "#grandTotal"
, oncalc: function (value, settings){
// you can use this callback to format values
$(settings.selector).val(value.toFixed(2) + "TL");
}
});
This would attach an event whenever the user finishes pressing a key in any input field that started with the id of "sum" and update the value in the #grandTotal element automatically. Maybe you don't need the "live" updating function--so this is just a suggestion.
Also, instead of using the selector "[id^=grandTotal]" I'd just use "#grandTotal"--which is much faster and efficient.
The syntax "[id^=grandTotal]" says look through *all* elements for ones that have an id attribute that starts with "grandTotal". That would match <div id="grandTotal1" />, <div id="grandTotal2" />, etc.
On the otherhand, the syntax "#grandTotal" is just a shortcut for document.getElementById("grandTotal")--which is way faster and efficient.
Is it possible to add some validation before the final result.
To be more precise i need to check if the result will be between 200 and 500. If it is less than i need this app to show the result as 200 and if it is more than 500 to show result as 500. But if it is between 200 and 500 it should display the exact value
for example
20*3 the result should be 200
51*8 the result should be 408
100*6 the result should be 500
i don't have lot of fields just two, the first which is changeable and the other is static
Thank a lot in advance
If you're using the calc() method, just use the complete callback to trigger some validation (which you're have to handle manually.)
$("[id^=total_item]").calc(
// the equation to use for the calculation
"qty * price",
// define the variables used in the equation, these can be a jQuery object
{
qty: $("input[name^=qty_item_]"),
price: $("[id^=price_item_]")
},
// define the formatting callback, the results of the calculation are passed to this function
function (s){
// return the number as a dollar amount
return "$" + s.toFixed(2);
},
// define the finish callback, this runs after the calculation has been complete
function ($this){
// DO VALIDATION HERE
}
);
Other question:
Trying something out i tried to make two fields changeable, so no matter what field the user edit (like in this example qty and price) the total should change. Do i have to bind something to both fields like
// bind the recalc function to the quantity fields
$("input[@name^=qty_item_]").bind("keyup", recalc);
and where to put this line of code for my other field???
Second question:
What if my table is generated dynamically based on a recordset. How to specify the IDs of those elements? i mean something like a shopping cart... If i have 10 items in my cart i would have 10 rows. Each row would have QTY field, price and total. How to make this plugin recognize how many rows there are and what are there ID's and do the math for each row.
I'm sorry to have so much questions but i really like your plugin and i want to use it as much as possible.
Thanks in advance.
If you want to recalc on each keypress, then you'd have to bind the behavior to any field where the user can press a key. Since jQuery is based on CSS selectors, you can use the comma as an "AND" selector, so something like this would bind to multiple elements:
$("#id1, #i2").bind("keyup", recalc);
As for the second question, you need to understand the CSS selector being used.
The "input[name^=qty_item_]" (drop the @, it's been depreciated) says "Grab all input elements with a name attribute starting with "qty_item_".
This means it would grab every element like:
<input type="text" name="qty_item_1" />
<input type="text" name="qty_item_2" />
<input type="text" name="qty_item_3" />
So, as long as your elements have a nomenclature like above, nothing in your jQuery code need to change.
Hi, I love ur plugin!
I have trouble to get it working with the European number format.
U described the chagnes for Stefan but the it seems it doesn't work with the current version anymore.
can u please give me the changes I have to do in the current version?
THX a lot!!
See this comment for how to use European formatted numbers:
http://blog.pengoworks.com/index.cfm/2008/2/20/Wat...
Got the same problem like stefan with the german/european comas, the output got every time points. I need 990,50 EUR not 990.5 EUR. How can I do that? I tried allready the hints of the other posts, but got no luck.
Thanks
Philipp
But here's a new issue. I have a time-entry system where people can enter their time worked for the day. There are several time entry fields on the form and they enter it in hh:mm format, so if someone worked on project A for an hour and 15 minutes, then they'd enter it as 1:15. So I want a field on the bottom of the column that will add up the fields and show total time. So the calculation must be done base 60 and account for the colons.
Any thoughts?
That's basically outside of the scope of the concept of the calculation plug-in. Time calculations are a completely different beast altogether. You can get into days, months, years, etc.
You're really going to want to create your own solution for your needs here. The calc plug-in is designed to help you solve generic math problems. I may eventually consider date/time functionality, but have no plans to do so currently.
First off great plugin. 10 points for ease of use.
I have come accross a bug. I noticed it on my site and after checking I get the same result on your demo page.
If you go to the first demo (the sum demo) and in the 4 boxes put:
10
9
5.1
5.21
The answer it gives is: 29.310000000000002
I looked at the source code and am at a loss as to why it happens.
Any ideas?
var total = 0;
total += 10;
total += 9;
total += 5.1;
total += 5.21;
alert(total);
I get the same nuts result. Must be a javascript funny....
I think total = total.toFixed(2); will solve my problem.
This is actually a precision issue. Here's some information that describes the issue:
http://bytes.com/topic/javascript/answers/518574-j...
To see a simple example of the precision issue, copy the text "javascript:alert(0.7 + 0.1)" (without the quotes) into your address bar and press [ENTER].
I'm sure you'll expect the answer to be 0.8, but instead you'll see the value is 0.7999999999999999.
I may look at adding some logic to help make the results more predictable.
However, you can resolve this issue by using the oncalc callback to simply format the number to the precision you want.
Sorry for all the noise.
Last night I decided to add some precision handling to the code. If you download the new version of the code the sum() and calc() method should automatically round numbers to the correct precision.
It does this by counting the max decimal places being used by the numbers and using that as a reference to figure out the precision.

this plug-in doesn´t work on IE 7.
regards,
Rui