PDA

View Full Version : Live Field Calculations



pimpmaster
11-05-2007, 05:48 PM
An image is worth a thousand words so:

http://funkysoulrebels.com/calc.gif

I basically want to have the user input the first two fields and have the last two get automagically calculated.

Field_3 = Field_1 - Field_2

Field_4 = (Field_1 Field_2) - 100

I've seen tons of examples using simple sums on text fields, but no idea where to start on something like this.

Any ideas?

Trinithis
11-05-2007, 10:51 PM
I think your formulas are wrong so I modified them. I think the form should display the following:
1000:1500:500:33.3333

In any case, here's the code.


<form>
<input type="text" id="cost" />
<input type="text" id="charge" />
<input type="text" id="profit" />
<input type="text" id="percent" />
</form>

<script type="text/javascript">
function addEvent(els, type, func) {
if(!(els instanceof Array)) els = [els];
for(var i=0, n=els.length; i<n; ++i) {
if(els[i].addEventListener) els[i].addEventListener(type, func, false);
else els[i].attachEvent("on"+type, func);
}
}

String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
};

var cost = document.getElementById("cost");
var charge = document.getElementById("charge");
var profit = document.getElementById("profit");
var percent = document.getElementById("percent");

addEvent(
[cost, charge],
"keyup",
function(e) {
var vcost = cost.value.trim();
var vcharge = charge.value.trim()
var ncost = vcost*1;
var ncharge = vcharge*1;
if(vcost=="" || vcharge=="" || isNaN(ncost) || isNaN(ncharge)) {
profit.value = percent.value = "";
return;
}
profit.value = ncharge - ncost;
percent.value = (ncharge - ncost) * 100 / ncharge;
}
);
</script>

pimpmaster
11-05-2007, 11:31 PM
Wow.. you certainly write some lovely code there. So clear and easy to follow!

It works a charm, although now just out of curiosity I wonder if I can extend it so that typing in a percentage will populate the profit and charge fields, and then after that maybe even get the profit field to affect the others. I can do each of them by itself no problem, but tend to trip over myself when trying to combine the functions. Still trying to wrap my head around how JS deals with arrays.

Anyways, thank you muchly for the assist :)

Trinithis
11-05-2007, 11:44 PM
If you do that, you will have to create a new event handler that consists of the %profit as the trigger. Aka, you will need a separate addEvent(a,b,c) function.

Remember, in order for what you want to do to work, you will either need to have the "Our Cost" field or the "What We Charge" field filled out in addition to the %profit.

pimpmaster
11-06-2007, 12:12 AM
Got it!


addEvent(
[cost, percent],
"keyup",
function(f) {
var vcost = cost.value.trim();
var vpercent = percent.value.trim();
var ncost = vcost*1;
var npercent = vpercent*1;
if(vcost=="" || vpercent=="" || isNaN(ncost) || isNaN(npercent)) {
profit.value = charge.value = "";
return;
}
profit.value = (ncost / 100 * npercent).toFixed(2);
charge.value = (ncost + (ncost / 100 * npercent)).toFixed(2);
}
);

You totally rock dude!

pimpmaster
11-06-2007, 12:35 AM
Actually, now that this is working, I have one last question.

At some point I will need to have multiple rows of these fields and I'm wondering how I can make this code more dynamic..IOW somehow pass the id's I want to use as arguments to functions...or something like that.

Just imagine me trying to use the above code on something like this:


<form>
<input type="text" id="cost_ac1500" />
<input type="text" id="charge_ac2500" />
<input type="text" id="profit_ac5000" />
<input type="text" id="percent_ac10000" /><br />

<input type="text" id="cost_df2500" />
<input type="text" id="charge_df5000" />
<input type="text" id="profit_df15000" />
<input type="text" id="percent_df20000" /><br />

<input type="text" id="cost_ry2500" />
<input type="text" id="charge_ry4000" />
<input type="text" id="profit_ry12000" />
<input type="text" id="percent_ry16000" /><br />
</form>

pimpmaster
11-06-2007, 12:50 AM
Woops!

After testing this out thoroughly its not working just yet. Im still stepping on my own toes it seems and the calculations are not accurate when I add multiple events.

Gonna sleep on this one and hack some more in the morning.

Edit: I lied. Stayed up a bit and fixed it. Now just have to make this modular and its a go.

Trinithis
11-06-2007, 01:53 AM
If you can guarantee that the input tags come as groups of four and contains no other input tags then you could do something like this:



<form id="form1">
<input type="text" />
<input type="text" />
<input type="text" />
<input type="text" /><br />

<input type="text" />
<input type="text" />
<input type="text" />
<input type="text" />
</form>

<script type="text/javascript">
function addEvent(els, type, func) {
if(!(els instanceof Array)) els = [els];
for(var i=0, n=els.length; i<n; ++i) {
if(els[i].addEventListener) els[i].addEventListener(type, func, false);
else els[i].attachEvent("on"+type, func);
}
}

String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
};


var fields = document.getElementById("form1").getElementsByTagName("input");

for(var i=0, n=fields.length; i<n; i+=4) {
addEvent(
[fields[i], fields[i+1]],
"keyup",
(function(i) {
return function(e) {
var vcost = fields[i].value.trim();
var vcharge = fields[i+1].value.trim()
var ncost = vcost*1;
var ncharge = vcharge*1;
if(vcost=="" || vcharge=="" || isNaN(ncost) || isNaN(ncharge)) {
fields[i+2].value = fields[i+3].value = "";
return;
}
fields[i+2].value = ncharge - ncost;
fields[i+3].value = (ncharge - ncost) * 100 / ncharge;
};
})(i)
);
}
</script>


I admit, this is a bit grungy, but I don't have time to create to create prettier code.

jscheuer1
11-06-2007, 05:30 AM
My never being one to worry too much about neat code:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css">
.calc, .calc table {
border-collapse:collapse;
margin:0;
}
.calc th {
font:bold 70% arial, sans-serif;
padding:.5em 0;
color:black;
text-align:left;
background-color:#c5c8b2;
text-indent:.5em;
}
.calc td {
background-color:#eceddf;
font:75% arial, sans-serif;
padding:.5em .5ex;
color:#8a8b7e;
}
.calc input {
width:7em;
}
</style>
<script type="text/javascript">
function tot(t){
var f=t.form.elements, e=['oc','ww'];
for (var i = 0; i < e.length; i++)
if(!f[e[i]].value||isNaN(f[e[i]].value-0))
return;
var m=f.ww.value-0, b=f.oc.value-0;
f.op.value=m-b;
f.pp.value=Math.round(((m-b)/b)*100);
}
</script>
</head>
<body>
<form class="calc" action="javascript:void(0)" onsubmit="return false;">
<table>
<tr>
<th>Our Cost</th><th>What We Charge</th><th>Our Profit</th><th>% Profit</th>
</tr>
<tr>
<td><input type="text" name="oc" onkeyup="tot(this);" onmouseover="tot(this);" onmouseout="tot(this);"></td>
<td><input type="text" name="ww" onkeyup="tot(this);" onmouseover="tot(this);" onmouseout="tot(this);"></td>
<td><input type="text" name="op" readonly></td>
<td>% <input type="text" name="pp" readonly></td>
</tr>
</table>
</form>
<form class="calc" action="javascript:void(0)" onsubmit="return false;">
<table>
<tr>
<th>Our Cost</th><th>What We Charge</th><th>Our Profit</th><th>% Profit</th>
</tr>
<tr>
<td><input type="text" name="oc" onkeyup="tot(this);" onmouseover="tot(this);" onmouseout="tot(this);"></td>
<td><input type="text" name="ww" onkeyup="tot(this);" onmouseover="tot(this);" onmouseout="tot(this);"></td>
<td><input type="text" name="op" readonly></td>
<td>% <input type="text" name="pp" readonly></td>
</tr>
</table>
</form>
</body>
</html>

Trinithis
11-06-2007, 06:24 AM
I meant better code . . . not neater code. :D

jscheuer1
11-06-2007, 08:58 AM
BTW, if you want to (using the code I wrote) get the % based upon the total charged rather than as a % of the base price:


<script type="text/javascript">
function tot(t){
var f=t.form.elements, e=['oc','ww'];
for (var i = 0; i < e.length; i++)
if(!f[e[i]].value||isNaN(f[e[i]].value-0))
return;
var m=f.ww.value-0, b=f.oc.value-0;
f.op.value=m-b;
f.pp.value=Math.round(((m-b)/m)*100);
}
</script>

jscheuer1
11-06-2007, 10:08 AM
Here's my self initialization version:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css">
.calc, .calc table {
border-collapse:collapse;
margin:0;
}
.calc th {
font:bold 70% arial, sans-serif;
padding:.5em 0;
color:black;
text-align:left;
background-color:#c5c8b2;
text-indent:.5em;
}
.calc td {
background-color:#eceddf;
font:75% arial, sans-serif;
padding:.5em .5ex;
color:#8a8b7e;
}
.calc input {
width:7em;
}
</style>
<script type="text/javascript">
function tot(){
var f=this.form.elements, e=['oc','ww'];
for (var i = 0; i < e.length; i++)
if(!f[e[i]].value||isNaN(f[e[i]].value-0))
return;
var m=f.ww.value-0, b=f.oc.value-0;
f.op.value=m-b;
f.pp.value=Math.round(((m-b)/b)*100);
}
tot.init=function(){
var f=document.forms, e='elements', ev=['keyup','mouseover','mouseout'],
a=function(el, e){el[e]=function(){tot.apply(el);};}, r=function(){return false;};
for (var i = 0; i < f.length; i++)
if(f[i].className=='calc'){
f[i].onsubmit=r;
for (var j = 0; j < f[i][e].length; j++)
if(/^(oc)|(ww)$/.test(f[i][e][j].name))
for (var z in ev)
a(f[i][e][j],'on'+ev[z]);
else if(/^(op)|(pp)$/.test(f[i][e][j].name))
f[i][e][j].setAttribute('readonly', 1, 0);
}
};
</script>
</head>
<body>
<form class="calc" action="javascript:void(0);">
<table>
<tr>
<th>Our Cost</th><th>What We Charge</th><th>Our Profit</th><th>% Profit</th>
</tr>
<tr>
<td><input type="text" name="oc"></td>
<td><input type="text" name="ww"></td>
<td><input type="text" name="op"></td>
<td>% <input type="text" name="pp"></td>
</tr>
</table>
</form>
<form class="calc" action="javascript:void(0);">
<table>
<tr>
<th>Our Cost</th><th>What We Charge</th><th>Our Profit</th><th>% Profit</th>
</tr>
<tr>
<td><input type="text" name="oc"></td>
<td><input type="text" name="ww"></td>
<td><input type="text" name="op"></td>
<td>% <input type="text" name="pp"></td>
</tr>
</table>
</form>
<script type="text/javascript">
tot.init();
</script>
</body>
</html>

Note: Once again, if you want the % to be of the total price charged change the last b to an m.

pimpmaster
11-06-2007, 01:06 PM
I have one last fish to fry before I go modular.

This one is truly baffling



var vdiscount = discount.value.trim();
var ndiscount = 1 - ((vdiscount*1)/100);

subtotal.value = (subtotal.value * ndiscount).toFixed(2);


I get some weird percentages thanks to Line 2. Apparently js doesn't like it when I subtract from 1.

As an example it tells me that 10% off on 100 = 89.1

If I get rid of the 1 - part, it all works flawlessly. :confused:

jscheuer1
11-06-2007, 05:26 PM
Both of my versions are already modular.

Trinithis
11-06-2007, 05:29 PM
I guess it is due to a rounding error? (If its not simply a logic error on your part.) If so, then this might work


var ndiscount = (100 - vdiscount)/100;

I just factored out the 1/100

In any case, John's code is good. You might want to consider using his.

pimpmaster
11-06-2007, 07:38 PM
Thanks for the code John..I cant believe how small a footprint it has. Of course I cant understand a word of it :P.

Trinithis's solution was more verbose, but I managed to twist it to suit my needs. Its actually pretty flexible, I can deduce fields in a variety of ways and even got some advanced discount math happening.

The only bad part is that I totally butchered the code :P

Seriously if any of you have suggestions on how I can suck less, I'm all ears.

Trinithis
11-07-2007, 01:32 AM
Seriously if any of you have suggestions on how I can suck less, I'm all ears.
Perhaps an explanation of John's code would help you learn a little :D

Expanded, his JS looks like:

function tot() {
var f = this.form.elements;
var e = ['oc','ww'];
for(var i=0; i<e.length; i++)
if(!f[e[i]].value || isNaN(f[e[i]].value-0)) return;
var m = f.ww.value-0;
var b = f.oc.value-0;
f.op.value = m-b;
f.pp.value = Math.round(((m-b)/b)*100);
}

tot.init = function() {
var f = document.forms;
var e = 'elements';
var ev = ['keyup', 'mouseover', 'mouseout'];
var a = function(el, e) {
el[e] = function() {
tot.apply(el);
};
};
var r = function() {
return false;
};
for(var i=0; i<f.length; i++)
if(f[i].className=='calc') {
f[i].onsubmit = r;
for(var j=0; j<f[i][e].length; j++)
if(/^(oc)|(ww)$/.test(f[i][e][j].name))
for (var z in ev)
a(f[i][e][j], 'on'+ev[z]);
else if(/^(op)|(pp)$/.test(f[i][e][j].name))
f[i][e][j].setAttribute('readonly', 1, 0);
}
};

And my explanation of it:

I think the first function, tot should be relatively easy to understand. It's much like the anonymous function I defined within the calls of my addEvent. All you need to know is that this refers to the form element that called it. More on that later.

tot.init you can think of as an init function with a namespace, tot. The code

e = 'elements'
is created to be used as shorthand for

f[i][e] == f[i].elements
In case you were wondering, you can access any property of an object using dot notation or array notation:

obj.prop == obj["prop"] //same thing!
Looking at the function a, all it does is take two arguments, an element and an event. Then it says el[e], which might describe something as follows:

//el.onevent = function(e) {...};
myElement.onclick = function(e) {...};
div1.onkeyup = function(e) {...};
Adding events this way is much like using addEventListener or attachEvent. The only difference is that you can't directly add two events of the same type to an element this way. (And addEventListener allows you to specify whether or not an event bubbles or not.)

In any case, the function being assigned to do the work of that event is tot.apply(el). This just means call the tot function, and wherever you see the keyword this, give it the value el.

The function r defined in here is just to make the form do nothing when you submit it when you later assign

f[i].onsubmit = r
The following loop looks at every form and sees if it has the class "calc". In other words, it looks for the appropriate forms to do its magic on. If a match is found, it then looks at all its elements (all the form-related tags (perhaps all its other child tags as well?) within it ... like the input tag) and tests to see if the name of the tag is "oc" or "ww". Thats what

if(/^(oc)|(ww)$/.test(f[i][e][j].name))
does. /^(oc)|(ww)$/ is a regex (regular expression) that sees if the characters "oc" or "ww" start and end the string it is tested against. In this case, that string would be the field name.

Should the regex test pass, then you know that the element (the tag) you are dealing with is either a "Our Cost" or a "What We Charge" input field. So we use a for-in loop to extract each element (not the same as an html element) in the array ev one at a time, which contains the values "keyup", "mouseover", and "mouseout". Within the loop, the extracted value is put into the variable z, and we call the function a with some paramaters, which, in all adds the events to the (html) element at hand.

Should the regex fail, we look and see if the element name is "op" or "pp". If it is, then make it read-only.

pimpmaster
11-07-2007, 02:58 PM
Thanks for taking the time to explain.. you make it sound so easy!

I suppose my problem with examples like these is that I tend to prefer explicit over clever code. I program in Ruby so maybe that has something to do with it.

I'm also trying to keep my JS as unobtrusive as possible, which is why your example (though not was neat and compact) is almost preferable since it works off DOM hooks. (Yes I am an HTML snob :p)

I'm going to read your post again a few times and let it sink in some more. Hopefully I will be able to extend John's code like I did with yours (minus the complete butchery)

Thanks to both of you for your sage-like patience :)

pimpmaster
11-08-2007, 12:54 AM
Argh.. I been trying to hook this up so that I can also type inside the percentage field and have that deduct the charge field (will do profit too once I figure this out) So far I havent been successful. I am guessing that perhaps I need to write a separate function for this??

Here is my tweak (pastied for extra pimp factor):

http://pastie.textmate.org/115275

Another issue I'm having is the fact that I need these multiple rows of inputs to live in one form. Is this possible?

pimpmaster
11-08-2007, 03:47 PM
LOL.. sometimes coding late at night is not the best idea. After racking my brain for a good hour, I realized that finding a charge by percent is a mathematical impossibility (unless I am calculating it off the cost, which is not what I want) Doh...

At this point I only have one obstacle left and thats the ability to have an unlimited number of these calculation rows inside a form, while not losing the ability to have multiple forms on a page.

Any ideas?

pimpmaster
12-22-2007, 03:23 PM
After attempting to bribe people to do this for me, I actually made some progress on my own:

http://funkysoulrebels.com/calculator/

By god I am starting to actually understand this!

There are a couple of things I am struggling with

1. No matter which way I try, the calculations only work within the scope of a form. I'd like to use one form, but with lots of "calc" fieldsets

2. I tried to use innerHTML and strong tags instead of inputs for the "each" fields, but cant get that working

3. Totally lost on how to clone the entire table

Any Ideas?

immortal_king
01-23-2011, 03:10 AM
It is really a great script youhave evolved. May I seek one more conditional request to be added to this script.

If there is one more drop down field with BOUGHT & SOLD as selections, and on selecting BOUGHT the existing script will work as it is and if SOLD is selected then a reverse calculation occurs ie. "cost-charge=loss" instead of "charge-cost=profit".

At the same time it is desired to have the script separately without having in form action, since te form can also be used for some other action like to post the values in database.

Thank you all great developers.