jQuery Calculation Plug-in: Making calculating easy...

Categories: JavaScript, jQuery

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:

alert( $(".price").sum() );

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:

calc.plugin.cart

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:

<table width="500">
<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:

// bind the recalc function to the quantity fields
$("input[@name^=qty_item_]").bind("keyup", recalc);

Our recalc() function looks like this:

function recalc(){
    // 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

Related Blog Entries

Comments

Rui Gomes's Gravatar hi,
this plug-in doesn´t work on IE 7.


regards,
Rui
Dan G. Switzer, II's Gravatar @Rui:

I just checked this in IE7 and it's working fine for me. Is there something specific that's not working for you?
Rui Gomes's Gravatar hi, thks for the reply.

if tested with your demo link and nothing (calculations) it´s working, but it works fine on FF.

regards,
Dan G. Switzer, II's Gravatar @Rui:

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.
Rui Gomes's Gravatar ok.

...and what might be filtering/blocking ?
i don´t have any firewall installed and i´ve stopped my anti-virus also.
Rui Gomes's Gravatar if you want i´ll send you a print screen of it.
Rui Gomes's Gravatar i´ll test on another IE7, ok ?

regards,
Dan G. Switzer, II's Gravatar @Rui:

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.
Rui Gomes's Gravatar ok, thks again for the reply.

regards,
Dan G. Switzer, II's Gravatar @Rui:

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. :)
Fontzter's Gravatar Hi Dan,

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
Dan G. Switzer, II's Gravatar @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.
Dan G. Switzer, II's Gravatar @Dave:

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.
Venkat's Gravatar Hi Dan,

Thanks for the nice plug-in.
How to calculate the time can u pl give an idea

Thanks
Venkat
:)
balboa's Gravatar how do i do calculation like this: (qty * price) + fee. is there another event other than keyup? coz i use select box to trigger the calculation
Dan G. Switzer, II's Gravatar @balboa:

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".)
Xevo's Gravatar Hey Dan,

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!
Dan G. Switzer, II's Gravatar @Xevo:

What's your code look like, is there an example I can see? Also, what problem are you having?
Lukas Stancik's Gravatar The sad thing is that I can not use this beatiful peace of code because whenever the form contains <input name="id"... jQuery throws an error 'z.indexOf is not a function'. I am not able to rename the input field because a lot of my CMS extensions depend on it :(
Dan G. Switzer, II's Gravatar @Lukas:

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
tom's Gravatar Hi Dan

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
Dan G. Switzer, II's Gravatar @Tom:

Take a look at the example of the shopping cart and look at the code for the calc() method. It shows off similar functionality.
hal's Gravatar This is great. I now need to be able to submit a form and post the individual item totals and the grand total. It's quite clear I am a real javascript newbie so any help is greatly appreciated.
Thanks, hal
Dan G. Switzer, II's Gravatar @Hal:

You should do the calculations on the server side. Never trust data coming from the client.
Jonne's Gravatar Thanks for this cool plugin. It was very easy to use!
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 !!!
Dan G. Switzer, II's Gravatar @Jonne:

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.
Jonne's Gravatar Great, and I figured out I should do that like this:
$("input[@name^=input_]").sum({bind:"keyup",oncalc:myOnCalc}, "#som");
Thanks !
jonne's Gravatar Oops, I posted that it worked immediately when I figured out the oncalc callback was called. But now instead of the totals, nothing is displayed. I see that the oncalc method has two arguments, the value and options. But what should the method do? I tried returning nothing, true, false, s, and tried to put s in the totals field. But in all cases, the end result is an empty total field. What am I doing wrong? Thanks.
function formatTotals(s, opt) { return s.toFixed(2); // test }
Stefan's Gravatar @Dan - Could you please provide an example of how to use the oncalc callback? Thanks!
Dan G. Switzer, II's Gravatar @Stefan:

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]").
Stefan's Gravatar @Dan: Thanks a lot! It's working!

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!
Dan G. Switzer, II's Gravatar @Stefan:

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.
Stefan's Gravatar Dan, it works like a charm! Thank you very much!
Christopher's Gravatar I love this plugin. I'm using it on a clients site and it's working great, but I'm having trouble doing something that a commenter mentioned earlier.

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.
Candi's Gravatar I am using your form and it works. But I am running into a problem that I can't solve as I am not that familiar with jquery. I use the grandTotal as a value in a form which will be submitted to a secure server. I've tried everything I can but can't seem to get this work. Is there any easy work around for this?
Dan G. Switzer, II's Gravatar @Candi:

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.
Cenk's Gravatar I love this plugin. I'm using your plugin in an online order form and it's working great. But this is the problem ;

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 :(
Dan G. Switzer, II's Gravatar @Cenk:

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.
Candi's Gravatar Thanks for responding. I ended up using a different calculating script. It's working great. It's too bad we can't use your plugin for quick calculating forms to be submitted to a merchant since it's so easy to implement. But I appreciate your time and energy!
Dan G. Switzer, II's Gravatar @Candi:

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.
Cenk's Gravatar @Dan :

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.
Dan G. Switzer, II's Gravatar @Cenk:

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.
Cenk's Gravatar @Dan:-) Thanks a lot again. It's working brillant :-)))
Benjo's Gravatar this is really a great plug-in. I'm now trying to develop an application but i need some help ...

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
Dan G. Switzer, II's Gravatar @Benjo:

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
   }
);
Benjo's Gravatar thanks lot. I added my validation at the point you showed me and it works great.
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.
Dan G. Switzer, II's Gravatar @Benjo:

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.
Benjo's Gravatar Thanks a lot, it works!
Philipp's Gravatar support for European number format.

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!!
Dan G. Switzer, II's Gravatar @Philipp:

See this comment for how to use European formatted numbers:

http://blog.pengoworks.com/index.cfm/2008/2/20/Wat...
Mang Dadang's Gravatar I love ur plug in. But, how do I get price an item from db using select box?
Philipp's Gravatar Hi Dan!

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
John's Gravatar Thanks for this library. Works great.
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?
Dan G. Switzer, II's Gravatar @John:

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.
rpcutts's Gravatar Hi Dan,

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?
rpcutts's Gravatar After placing this code in a blank page:

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.
Dan G. Switzer, II's Gravatar @rpcutts:

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.
rpcutts's Gravatar Yes I've been educated on the fun world of floating point.
Sorry for all the noise.
Dan G. Switzer, II's Gravatar @rpcutts:

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.
Jubal's Gravatar I think I might misunderstand the application of this calculation tool.

My desire is to have a form (particularly your quantity/item example) that does calculation on the fly and passes the final amount to a payment processor. From the comments I'm reading so far here, it appears best practice is NOT to use the returned amount via JavaScript, but rather, to do the calculation server-side.

I freely admit I've got a lot to learn about JavaScript and DOM, and that I might simply have failed to grasp a concept or three.

That said, how on Earth do I safely pass a calculated-on-the-fly total value to a hidden input field that is form-submitted?

Thanks in advance.
Dan G. Switzer, II's Gravatar @Jubal:

You are correct--you should never trust values coming from JavaScript (aka the client.) All pricing information should come directly from a protect location (such as your controlled server.) If you were to accept dollar amounts coming directly from a hidden form field or from the user's browser, it would be very easy for a knowledgeable user to post whatever dollar amounts they wanted.

The purpose of this plug-in is to help you with the task of building a better UI. Users like to see totals updated in real-time and this will help you easily build an interface to show totals get updated in real-time. However, you just shouldn't be trusting of these values.
Njau ndirangu's Gravatar I updated the to jquery 1.3.2 and jquery calculation 0.4 and the code broke. jquery 1.2.6 works fine with jquery calculation 0.4. Anybody else having this problem
Dan G. Switzer, II's Gravatar Make sure you're not using old style attribute selectors (with the @ character)--this syntax no longer works in jQuery v1.3.x (it was depreciated in v1.2.x.)

So, lines like $("input[@attr=...]") should be $("input[attr=...]").

I've tested the plug-in with jQuery v1.3.x and it works fine. The example page is running v1.3.1 of the Google CDN.

Lastly, make sure you're running the latest version of the plug-in.
Njau's Gravatar It works now, thanks
Mike's Gravatar Dan,

I'm not able to get this to work. All I'm getting is sum() (not just sum, but all the functions) not defined.

Here is my example.

http://mercury.hamilton.edu/devmpstone/forms-pdf/c...?
Dan G. Switzer, II's Gravatar @Mike:

If you change:

$(document).ready(function(){

to:

$(window).load(function(){

Does it work?
Mike's Gravatar Dan,

No, it doesn't fix it.
Mike's Gravatar Dan,

There is a problem with your .js file.

Downloaded the .min.js file and everything seemed to work.

Might want to take a look at it.

Best regards,

Mike
Michael's Gravatar Dan,

If you've got time, could you take a look at:

http://mercury.hamilton.edu/devmpstone/forms-pdf/e...

Having trouble correctly calculating this very large table.

Maybe/Hoping you've got an idea.

Thanks.
Dan G. Switzer, II's Gravatar @Michael:

Could you be more explicit about the actual problem you're having?
Keith's Gravatar Hi Dan,

this is a great plugin, I really am appreciating it.

I was wondering if you could help me with this, I am trying to do two calculations from the same fields, and have them both bind to one click event. I have been unsuccessful in getting it to work.
any suggestions?

Cheers
-Keith
keith's Gravatar Sorry,

didn't post my URL

http://leftcoastgeek.com/calc/

thanks!
Dan G. Switzer, II's Gravatar @Keith:

You've got your functions defined incorrectly, use this instead:

function incSales(){
   $("#incSales").calc(
         "revenue * (margin / 100)",
         // define the variables used in the equation, these can be a jQuery object
         {
            revenue: $("input[name^=annRevenue]"),
            margin: $("input[name^=margin]")
         },
         // define the finish callback, this runs after the calculation has been complete
         function ($this){
            // sum the total of the $("[id^=total_item]") selector
            var sales = $this;

            return sales.toString() + "M";
         }

   );
}

function incProfit(){
   $("#incProfit").calc(
         "revenue * margin",
         // define the variables used in the equation, these can be a jQuery object
         {
            revenue: $("input[name^=annRevenue]"),
            margin: $("input[name^=margin]")
         },
         // define the finish callback, this runs after the calculation has been complete
         function ($this){
            // sum the total of the $("[id^=total_item]") selector
            var sales = $this;

            return "$" + sales.toString();
         }

   );
}
Keith's Gravatar Dan,

that was just what I needed, Thank you!
Marc's Gravatar Hi Dan,

Can you please check my code? I'm a jquery nOOb but I did my best. Here's my code...

<script type="text/javascript">
   var bIsFirebugReady = (!!window.console && !!window.console.log);

   $(document).ready(
      function (){

         // automatically update the "#totalSum" field every time
         // the values are changes via the keyup event

         $(".row").find(".sum").sum("keyup", $(".row").find(".totalSum"));

      }
   );
</script>



<table width="900" border="0">
<tr id="tr_1" class="row">
<td><input type="text" id="1_1" class="sum" value="2" size="2" /></th>
<td><input type="text" id="1_2" class="sum" value="2" size="2" /></th>
<td><input type="text" id="1_3" class="sum" value="2" size="2" /></th>
<td><input type="text" id="1_4" class="sum" value="2" size="2" /></th>
<td><input type="text" id="1_total" class="totalSum" value="" size="2" readonly="readonly" /></th>
</tr>

<tr id="tr_2" class="row">
<td><input type="text" id="2_1" class="sum" value="2" size="2" /></th>
<td><input type="text" id="2_2" class="sum" value="2" size="2" /></th>
<td><input type="text" id="2_3" class="sum" value="2" size="2" /></th>
<td><input type="text" id="2_4" class="sum" value="2" size="2" /></th>
<td><input type="text" id="2_total" class="totalSum" value="" size="2" readonly="readonly" /></th>
</tr>
</table>

What I wanted to do is that each <tr> will act as a "parent" so every row will have independent totalSum. I'm gonna use ASP:repeater which means all ID's of rows and textbox will be dynamically generated so I'll be relying on my "class" names.

What I was trying to achieve is something like this:
$(this).(".row").find(".sum").sum("keyup", $(this).(".row").find(".totalSum"));

but of course it won't work - I can't nail the right syntax! :(

Thank you for your help
Dan G. Switzer, II's Gravatar @Marc:

What you're needing to do is something like:

$("#tr_1").find(".sum").sum("keyup", "#1_total");
$("#tr_2").find(".sum").sum("keyup", "#2_total");

Since you don't really want to do that for each row, you can make things dynamic:

$("tr.row").each(function (){
var n = this.id.split("_")[1];
$(this).find(".sum").sum("keyup", "#" + n + "_total");
});

This will loop through each table row and run the run the appropriate sum() for each table row.
Farshad's Gravatar Hi
Thanks for your plugin, I need to know if its possible to use this plugin with radiobutton and checkboxes ?
for example we have a list of radio buttons that user can select them , then this plugin calculate them ?
Thanks
Dan G. Switzer, II's Gravatar @Farshad:

You should have no problems using any type of form element. You may need to use the Field plug-in, but there should be no issues using any form element.

You can get the Field plug-in here:
http://plugins.jquery.com/project/field
Farshad's Gravatar I can use this plugin with radio buttons, but it will sum all the values of the radiobuttons, i want be able to sum the values of only selected radio buttons, how can i don that ?
Dan G. Switzer, II's Gravatar @Farshad:

I can tell you that it does work, but you will probably need to use the Field plugin (as I stated above) and without seeing code, I can't give you guidance.
Farshad's Gravatar For example look at this page :
https://www.psd2html.com/order-now.html
when you change the order details with radio buttons the total price will change too
Ben 58's Gravatar Great plugin ! Is it possible to include a 'Square Root' in a function ?
Thanks !
Dan G. Switzer, II's Gravatar @Farshad:

I know what you want and the calc plug-in will work, but you must use the field plug-in.
Dan G. Switzer, II's Gravatar @Ben 58:

Square rooting a number is a pretty specific task and would be much easier to just write specific code to do that. It's not a situation where this plug-in would really save you any coding.
jimjim's Gravatar Really appreciate your plugin. I'm having some trouble with the following:

1. I add form elements on the fly and would like to sum their values, but .sum only seems to work with what's there when the page loads. I'm thinking there should be a way to use .live?

2. I need to select the elements by both the beginning and end of their name or id. I'm dealing with names like product_nnn_id and product_nnn_value where the nnn varies and would like to sum all the product_nnn_value's. I'm sure there's a way to do this in the selector, but I can't find it.

Thanks for putting this out there.
Dan G. Switzer, II's Gravatar @jimjim:

First, remember that the $.live() event handler only works with certain events. Many form-specific events aren't currently supported.

Second, you can use $.live(), but you won't be able to use the help arguments (which automatically bind the sum() to field.) Instead you'll need to build the functionality manually, something like:

var $sum = $(":input.sum").live("keypress", function (){
$("#total").html($sum.sum());
})

(I didn't test the code, but something like that will work.)

As for your selector problem, I'm not aware of a way to find dynamic values sandwiched like that.

What I would recommend is just adding a class to each element such as "product_values". That will allow you to query the fields you need to sum by class name instead of the name attribute.
jimjim's Gravatar Thanks for a quick response. I think I'm close. Regarding the selector, I was looking for a way to select on everything but the dynamic values. What's listed below solves the selector issue. It does not however update my total. Any ideas?

$(function() {
var line_items = $("input[name^=opportunity[line_items_attributes]][name$=[value]]").live("keyup", function(){
var sum = line_items.sum();
$("#totalSum").val(sum);
});
});
Dan G. Switzer, II's Gravatar @jimjim:

Sorry for misleading you in my last post, but you won't be able to save the results of the selector in a variable since your results may change at any time. You'll either:

1) Have to re-run the selector each time
2) Maintain a separate queue of known input.

Option 1 is the easiest, but it might be really slow depending on how many elements you're dealing with:
$(function() {
   var selector = "input[name^=opportunity[line_items_attributes]][name$=[value]]";
   var line_items = $(selector).live("keyup", function(){
      var sum = $(selector).sum();
      $("#totalSum").val(sum);
   });
});

Option 2 is more work. It'll require you building a queue of known matching elements.
jimjim's Gravatar Ha! It works! I've added some to set the total initially and to update the total when I remove one of the fields. Won't be more than 5 or 6 fields and it seems plenty fast.

FYI - using this with the Rails Complex Forms + JQuery example / pattern.

Thanks again.

$(function() {
var selector = "input:visible[name^=opportunity[line_items_attributes]][name$=[value]]";
var total = $(selector).sum();
$("#totalSum").val(total);
$(selector).live("keyup", function(){
var total = $(selector).sum();
$("#totalSum").val(total);
});
$("a:contains('remove')").live("click", function(){
var total = $(selector).sum();
$("#totalSum").val(total);
});
});
web designer's Gravatar Just like Farshad Im trying to use this plugin with radio buttons, but can't get it to work. Im using the field plugin as instructed, but I can't seem to make anything work. Im trying to copy the quantity amount demo, but with radios...
camcam's Gravatar hello, this is a great script, however im fairly new into jquery and what not, but i seem to be having the same issue as @jimjim. I am also creating the form dynamically and the fields created after loading don't pull in the calculation. what can i do to get this running? thanks for all the help!
cameron
Martin's Gravatar Hi

Amazing plugin but still find it hard to get it do what i want.

I want a form that calculates values of checkboxes en selectboxes.
It should put different categories in a sub total and at the end it should calculate the grand total price.

I got it so far that is calculates the sub total but somehow the grand total doesn't work. and also when i first load the pages it doesn't load the standard decision only when you click it

Can someone help me on my way ?

Url : http://web.productieserver.nl/martin/rekenModule/

Javascript :

$(document).ready(
   function (){

      $.Calculation.setDefaults({
         // Amerikaanse prijs formaat omzetten naar europees
         //reNumbers: /(-|-\$)?(\d+(\.\d{3})*(,\d{1,})?|,\d{1,})?/g
         reNumbers: /(-|-\$)?(\d+(\.\d{3})*(\,\d{1,})?|\,\d{1,})?/g
         // define a procedure to convert the string number into an actual usable number
         , cleanseNumber: function (v){
            // cleanse the number one more time to remove extra data (like commas and dollar signs)
            // use this for European numbers: v.replace(/[^0-9,\-]/g, "").replace(/,/g, ".")
            return v.replace(/[^0-9,\-]/g, "").replace(/,/g, ".");
         }
      });
   
      // optellen van Selectboxes
      $("select").change(function() {
       var total = 0;
       $("select option:selected").each(function() {
       total += parseFloat($(this).val());
       });
      
       $("#total_1").text(
            total.toFixed(2).replace(/\./,',') + " EUR"
         );
         
      });
      
      // optellen van radio buttons
      $("input[type=radio]").click(function() {
       var total = 0;
       $("input[type=radio]:checked").each(function() {
       total += parseFloat($(this).val());
       });
      
       $("#total_2").text(
            total.toFixed(2).replace(/\./,',') + " EUR"
         );
         
      });
      
      
      // Totaal uitrekenen
      $(".total").change(function() {
       var total = 0;
       $(".total").each(function() {
       total += parseFloat($(this).text());
       });
      
       $("#grandTotal").text(
            total.toFixed(2).replace(/\./,',') + " EUR"
         );
         
      });
      
   }
);
Randy Parker's Gravatar This line in version 0.4.07:
var tmp = cbFormat.apply(this, [exprValue])
is missing a terminal semicolon; which confuses javascript compressors. Adding the line terminator cures the "missing ; before statement" javascript error.
Dan G. Switzer, II's Gravatar @Randy:

Good catch. I try to always terminate lines with a semi-colon, but mistakes happen.

Thanks for the report!

I've corrected it in my source code. The fix will be in the next release.
Tim's Gravatar Is there any way to change the price based on quantity - like if you buy 10 or more Learning jQuery books the price becomes 29.99? Seems like it should be easy but I haven't been able to get to work - thanks for the plugin
Dan G. Switzer, II's Gravatar @Tim:

The calc plug-in isn't designed to do all the work for you. It's just a helper library to help reduce the amount of code you have to write for repetative functionality. So this isn't something the calc plug-in has built-in.

However, it's still easy to implement. All you need to do is monitor the quantity field and update your value based upon your pricing scheme.

The calc plug-in reads the values in on each execution, so if you change the price the next time calc functions are run the correct price will be calculated.
Rhapsody's Gravatar I tried your solution for Mark (8/18/09).
$("tr.row").each(function (){
var n = this.id.split("_")[1];
$(this).find(".sum").sum("keyup", "#" + n + "_total");
});

It works perfectly!

The scenario that I am using it in is a little different. In that there are no input fields -- only spans, and I am using the class to calculate the sum. Now I am using this plug-in in conjunction with another, edit-in-place plug-in. What happens is that when a user clicks on the numeric value, an input field (with a class called "inplace_field") is dynamically added inside of the .sum span. This, quite predictably causes the newly entered number to not be registered by the code. I am looking for a way to tell the code to look for the child(ren) of .sum also, and if it exists use that as well in the calculation. Thank you.
Toto's Gravatar I need a little help if you don't mind.

In the following code everything works, except the callback function (oncalc). It's never applied.

$(document).ready(function (){
$('input.BilanExploitationPM_BilanSynthetiquePM_EnActif').sum({
bind: 'keyup',
selector: 'input#Total_BilanExploitationPM_BilanSynthetiquePM_EnActif',
oncalc: function (settings, value) {
$(settings.selector).val+(value.replace(/\./g, ','));
}
});
});

Is it even possible to use a callback function with the sum() method?

Thanks for your help.
Toto's Gravatar The "+" after "val" is just a typo. Sorry. The question is still on.
Toto's Gravatar I'm stupid, stupid, stupid.

I wrote "oncalc: function (settings, value)" instead of "oncalc: function (value, settings)". I don't how I ended up doing this as I really thought I copy/pasted the code.

You can delete this post and both the previous ones.
Rhapsody's Gravatar I am using the following code to calculate the total in all elements with class "sum"

$(function()
{
                              $("tr.row").each(function (){
                              var n = this.id.split("_")[1];
                              $(this).find(".sum").sum("keyup", "#" + n + "_total");
                              });

});

The problem arises when an input is added dynamically to the element with class "sum".

This works fine:
<tr id="tr_#myReport.currentRow#" class="row">
<td>
<span class="sum" id="qp_#myReport.Uid#" >#DepositQP#</span>
</td>
<td>
<span class=" sum" id="rg_#myReport.Uid#">#DepositReg#</span>
</td>
<td>
<span class="totalSum" id="#myReport.currentRow#_total"></span>
</td>
</tr>

But when an input is added within one of the "sum" spans, predictably, it fails:

<tr id="tr_#myReport.currentRow#" class="row">
<td>
<span class="sum" id="qp_#myReport.Uid#" >#DepositQP#</span>
</td>
<td>
<span class=" sum" id="rg_#myReport.Uid#">#DepositReg#
<form class="inplace_form">
<input class="inplace_field" value="inplace_value">
</form>
</span>
</td>
<td>
<span class="totalSum" id="#myReport.currentRow#_total"></span>
</td>
</tr>

Any suggestions to make it work?

Thanks.
Dan G. Switzer, II's Gravatar @Rhapsody:

When you add new rows, you'll also need to invoke the $.sum() call on the new rows.
Rhapsody's Gravatar Thanks for your response Dan. It is invoked for each row, but when a dynamic input field is added to the "sum" elements, it does not register it, because the newly added field is an element within "sum" class. Here is a demo page: http://intranet.gcitexas.com/demo.htm. Perhaps that would help my explanation.
Dan G. Switzer, II's Gravatar @Rhapsody:

This would be the expected result with jQuery. Calling a selector on the page only effects the elements in the DOM at the time when you ran the initial code, future elements would be unaffected.

So, my comment still remains the same--whenever you add new fields, you're going to need to invoke the $.sum() method on those fields.
Derek's Gravatar Hello Dan,

Great plug in. If I wanted to sum all the values within the page that had the id="item-total-#" across the page. Where # is the Item number.

The expected result would be
#grandTotal1 = 11
#grandTotal2 = 22

Could you please provide some tips as to how I would be able to achieve my expected results using your plug-in?

Thanks in advance,
Derek

<table>
<tr>
<td>Item 1 Total</div></td>
<td><div id="grandTotal1">0</div></td>
</tr>
<tr>
<td>Item 2 Total</div></td>
<td><div id="grandTotal2">0</div></td>
</tr>
</table>

<h1>Shipment 1</h1>
<table>
<tr>
<td>item 1 total</td>
<td><div id="item-total-1">1</div></td>
</tr>
<tr>
<td>item 2 total</td>
<td><div id="item-total-2">2</div></td>
</tr>
</table>

<h1>Shipment 2</h1>
<table>
<tr>
<td>item 1 total</td>
<td><div id="item-total-1">10</div></td>
</tr>
<tr>
<td>item 2 total</td>
<td><div id="item-total-2">20</div></td>
</tr>
</table>
Dan G. Switzer, II's Gravatar @Derek:

The ID attribute on an element must be unique--so by repeating ID values you're creating invalid HTML and jQuery will only find the first matching ID.

If you change the ID attributes above to the CLASS attribute, then you should be able to do:

$("item-total-1").sum("keyup", "#grandTotal1");
$("item-total-2").sum("keyup", "#grandTotal2");

-Dan
Derek's Gravatar Thanks for the quick response Dan.

I've changed my item-total IDs tags to CLASS tags and included the jQuery statments that you have provided, but the #grandTotal IDs do not get updated with the sum. Any ideas as to what I may be doing wrong?

Thanks,
Derek

<table>
<tr>
<td>Item 1 Total</div></td>
<td><div id="grandTotal1">0</div></td>
</tr>
<tr>
<td>Item 2 Total</div></td>
<td><div id="grandTotal2">0</div></td>
</tr>
</table>

<h1>Shipment 1</h1>
<table>
<tr>
<td>item 1 total</td>
<td><div class="item-total-1">1</div></td>
</tr>
<tr>
<td>item 2 total</td>
<td><div class="item-total-2">2</div></td>
</tr>
</table>

<h1>Shipment 2</h1>
<table>
<tr>
<td>item 1 total</td>
<td><div class="item-total-1">10</div></td>
</tr>
<tr>
<td>item 2 total</td>
<td><div class="item-total-2">20</div></td>
</tr>
</table>
AdamG's Gravatar Hi Derek

Thank you for your excellent plugin. I'm using it on a conference website to calculate registration costs (yes, and cleaning the input server side).

I'm try to apply a discount based on the number of people attending and have this working correctly, however, I can't get the discount to update after it has been triggered. I.e. The total quantity gets to 10, and the discount is applied correctly, but then if the total qty value is reduced the discount isn't being updated. Everything else, the totals, etc, is working fine. This is the code:

// FORM CALCULATION

// bind the recalc function to the quantity fields
$("input[name^=conf_qty_]").bind("keyup", recalc);
// run the calculation function now
recalc();

function recalc(){
   $("[id^=conf_total_]").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^=conf_qty_]"),
         price: $("[id^=conf_cost_]")
      },
      // 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){
         // sum the total of the $("[id^=conf_total_]") selector
         var sum = $this.sum();
         
         $("#conf_subtotal").text(
            // round the results to 2 digits
            "$" + sum.toFixed(2)
         );
      }
   );
   // Discount
   $totalqty = $("input[name^='conf_qty_']").sum("keyup", "#conf_discount_qty_total");
   $totalqty_value = $('[name=conf_discount_qty_total]').val();
   if($totalqty_value >= 10) {
      $("[id^=conf_discount_group]").calc(
         // the equation to use for the calculation
         "price - (discount * price)",
         // define the variables used in the equation, these can be a jQuery object
         {
            discount: .05,
            price: $("[id^=conf_subtotal]")
         },
         // 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);
         }
      );
   }
}
Dan G. Switzer, II's Gravatar @AdamG:

I'm not sure I understand your question. You have code that looks like it's specifically preventing the discount from being applied if less that 10:

if($totalqty_value >= 10)
Derek's Gravatar Hey Dan,

I just wanted to let you know that I leveraged your example in the calculation plug-in main page to solve my problem.

$("#idTotalTextSum").click(
   function (){
   // get the sum of the elements
   var sum = $(".textSum").sum();

   // update the total
   $("#totalTextSum").text("$" + sum.toString());
            }
);

Thanks again for this awesome tool.

-Derek
Dan's Gravatar Hello, I was wondering... is there a way to store that final grandTotal value in a php variable so that I can pass that along through forms and such ? Thank you very much, great support.
Dan G. Switzer, II's Gravatar @Dan:

You should do that type of calculation in PHP. You should never trust calculations coming from the client-side--they are too easy to manipulate.
Dan's Gravatar I agree, however I already spent a lot of time implementing your method in my little project for school. No security issues will occur because this project is to be used only locally. So, can I please hear a tip or a suggestion on how to catch that grandTotal variable. Thank you and congrats for your great support !
Dan G. Switzer, II's Gravatar @Dan:

You can use the complete callback to update a hidden form field. In the example above, you'd change the code:

function (s){
// return the number as a dollar amount
return "$" + s.toFixed(2);
},

to something like:

function (s){
var value = "$" + s.toFixed(2);
// update hidden form field
$("#grandTotal_field").val(value);
// return the number as a dollar amount
return value;
},
Randy's Gravatar How do I handle a web form whose sum equation includes a term that is a product of two html fields? Like this $("#a, #b, #c").sum where there is no actual #b element, instead there are #d and #e, which have to be multiplied together.

I can create an otherwise unneeded #b subtotal element (perhaps hidden), but the keyup event is never produced from it. The elements producing keyup are #d and/or #e, and the sum method is not listening to them. Of course, tabbing through one of the registered fields works, but until then, a user sees no change in the sum.

Also, the addition of a 'multiply' method would be more convenient than setting up 'calc()'
Randy's Gravatar I added an event trigger to the 'finish callback' of 'calc()':

$('#a').trigger('keyup'));

Where #a is one of the elements registered in sum(). The calc() I'm referring to is used to multiply #d x #e.
Dan G. Switzer, II's Gravatar @Randy:

You wouldn't be able to use the sum() method to do what you want. You'd need to use the calc() method.

Just as a reminder, these are just helper functions to help with the most common tasks. There are times when just writing custom code is going to be the best route to go. However, you should be able to do what you want with the calc() method, just make sure to use the appropriate parenthesis in your equation (i.e. "a + (d * e) + c".)
Błażej Kosmowski's Gravatar Quick question - I would like to update not only quantity * price in my invoice lines, but brutto value and vat value as well (per invoice line) - how can I approach this?

Tried something like this (not working properly though)

function recalc() {

//netto_value
$("[name$='[netto_value]']").calc(
"qty * price",
{
qty: $("input[name$='[quantity]']"),
price: $("[name$='[netto_price]']")
},
function (s) {
return s.toFixed(2) + " PLN";
},
function ($this) {

var vat_value = parseFloat($("[name$='[netto_value]']").val()) * parseFloat($("[name$='[vat_rate]']").val());
var brutto_value = vat_value + parseFloat($("[name$='[netto_value]']").val());
$("input[name$='[vat_value]']").val(vat_value);
$("input[name$='[brutto_value]']").val(brutto_value);


}
);

}
Dan G. Switzer, II's Gravatar @Błażej:

Just write a separate call to the calc() method for the other fields.

If you're really want to update the values on when this particular calc() method is running, then you'll need to use a nomenclature for your fields that allow you to easily grab the correct corresponding fields for the current row.
Tarek's Gravatar thanks 4 that great plug-in it really helps a lot
i have a question about this plug in
i am trying to use it with quantity and price
put the problem is that i am adding form elements of the quantity and price by java script and they are incrementing like q1,p1-----q2,p2...etc
i am wondering how can i make the calculation plug-in read the added field coz it only reads the ones added by HTML
Dan G. Switzer, II's Gravatar @Tarek:

All you need to do is use jQuery's native live() method and manually call the method you want. You can not use the calc's native binding functions, unless you bind the new fields you add via JS.
Tarek's Gravatar how can i deduct number from the total??
Dan G. Switzer, II's Gravatar @Tarek:

You'll need to use the calc() method--or just manually perform the math on a callback.

Add Comment

Leave this field empty


If you subscribe, any new posts to this thread will be sent to your email address.