Sometimes the fields of a form are part of a chain of a calculation. For example, when the price of an order position changes, its price including tax should be recalculated. Then the total price (including other additional costs) should also be updated. This means each input element (text, select box, ...) needs an onChange (or onBlur) function that recalculates the new results and triggers other (dependent) fields to update themselves.
Wouldn't it be better to write a formula and add it to a DOM element, and this one would be updated immediately when one of the formula's operands changes?
The Calculator plugin was designed to do that. A developer writes a formula and adds it to an input field or DOM element (say a table cell) and the plugin adds update trigger functions to each input field (or DOM element) that is used a an operand.
var formulaString = '{{left}} * doSomethingMore({{right}})'; $('#result').addFormula(formulaString); ... <input type="text" name="left"> {{left}}<br> <input type="text" name="right"> {{right}}<br> <hr> <input type="text" id="result">
doSomethingMore(...) is a JavaScript function that returns a numeric value, like function doSomethingMore(v) { return 2 * v; }.
The methods .addFomula(), .aggregate() and .aggregateTo() return the this variable. Thus these methods are chainable.
This method adds a new calculation function to a (wrapped) DOM element or returns an array of calculations functions added to this DOM element.
The wrapper must contain only 1 (one) DOM element. If there are none or more than 1, no operation is performed.
If the method is called without a newCalcFunction parameter, the method returns an array of functions or undefined/null if no calculation function was set yet.
Normally this method is used internally by .addFomula() or .aggregate()/.aggregateTo() to add functions to an operand field.
The function passed as parameter normally takes all operands, calculates the result and sets it to the result field's value or result element's innerHTML. The function doesn't expect any parameter nor uses the this variable.
This function object is not just a method, it also holds the configuration of the plugin.
the number of decimal digits.
$('#result').addFormula('{{#a}} + {{#b}}')
This method generates a calculation function by interpreting the formulaString parameter and adds that function to each operand field.
The example above shows how a formula could look like. It contains two operands ({{#a}} and {{#b}}). What does "{{#a}}" and "{{#b}}" mean? It means that the element or input field with ID="a" ({{#a}}) is used as left-hand operand and the element or input field with ID="b" ({{#b}}) as the right-hand operand.
Internally this notation is called "accessor". An accessor could contain:
If there are more than one field with the same name within the page, the plugin uses the 1st one.
If there is an input field with ID="a", {{a}} or {{#a}} will mean that field. {{a}} could also mean name="a", but IDs are searched first, then names and then JQuery selectors.
Accessors could also describe default values. Thus if an input field is empty, the default value (if set) is used. "{{a || 33}})" tells that if the input field (with id="a" or name="a") is empty, a value of 33 is used as operand.
The plugin itself does not contain any formula parser. The idea was that JavaScript already has one: the operands are replace by some JavaScript code, then the eval() function of the JavaScript interpreter is used to generate the calculation function. The generated function does not call eval().
As described above the plugin uses JavaScript's build-in function eval() to generate a calculation function. This means a formula could contain calls to any JavaScript function that receives a numeric value and returns a numeric value, like Math.sqrt(x), Math.pow(2, x), ...
var formula1 = '{{left}} * Math.pow(2, {{right || 1}})"; var formula2 = '{{[name ^= "sum"]}} * 1.5';
This method generates a so-called aggregation function and adds it to the input fields selected by the a JQuery selector. Internally this method uses .calculation().
$('#aggResult').aggregate('[name ^= "left"]', function(a, b) { ... });
This line adds an aggregation function to all input fields that match the selector "[name ^= "left"]". The generated function writes its result to the input field (or element) with id="aggResult".
The 2nd parameter of .aggregate() is a function that will be invoked to calculate the result. If this parameter is omitted an exception is thrown.
An aggregatorFunction is called for each operand in the aggregation set. The function is called with tree parameters:
An example of an aggregator function that adds the operands could look like:
function add(currentResult, nextValue, ignoredNumberOfOperands) { if (currentResult == null) { return nextValue; } return currentResult + nextValue; }
See also Calculator Extension Plugin. There are some pre-defined aggregator like sum() and avg().
Now say there is an aggregation of the fields [ a, b, c, d, e, f, g ] collected by the selector myOperandsSelector, and these fields have the values [ 1, 2, 3, 4, 5, 6, 7 ] and the previously declared add function is set as an aggregator function:
$(myTargetSelector).aggregate(myOperandsSelector, add);
That add function will be called seven time (once per not-emtpy operand). The following code displays what happens:
add(null /* 1st call */, 1 /* a */) { return 1; } add( 1, 2 /* b */) { return 1 + 2; } add( (1 + 2), 3 /* c */) { return 3 + 3; } add( ((1 + 2) + 3), 4 /* d */) { return 6 + 4; } add( (((1 + 2) + 3) + 4, 5 /* e */) { return 10 + 5; } add( ((((1 + 2) + 3) + 4) + 5), 6 /* f */) { return 15 + 6; } add((((((1 + 2) + 3) + 4) + 5) + 6), 7 /* g */) { return 21 + 7; }
Another example for an aggregator function could look like the following. This function differs in the fact that it has some kind of state. This state is stored in local variables of the outer factory function that returns the real. aggregator function. createAvgAggregateFcn() creates an aggregator function that is passed to .aggregate().
function createAvgAggregateFcn() { var ii = 0, sum = 0; return function(total, nextVal, ignoredNumberOfOperands) { if (!total) { ii = sum = 0; } return (sum += nextVal) / ++ii; }; } $('#AVG').aggregate('[name ^= "avgop"]', createAvgAggregateFcn());
Consider that the returned function cannot be reused for other .aggregate() calls, because of the variables ii and sum.
This method is similar to .aggregate(). But this time the fields of the aggregation are determined by a JQuery query and the result is written to the field that is determined by the selector given as first parameter to .aggregateTo().
This method is also called with a 2nd parameter. This one is an aggregatorFunction as described above.
This method uses .aggregate().
Consider that all results calculated by .addFormula() or .aggregate() are made on the client for the client. These values are good for improving the user experience, but when sending the calculated values to the server they should not be trusted without checking!
The triggers for updating input fields or elements are made by using eval(), the created triggers themselves do not contain any eval()-calls (if it is not called explicitly within a formula). This means when creating formulae dynamicaly by using any values from a DB, file system, 3rd party Web Service or what ever, You should be aware that these values run through an eval()-call and should be escaped to avoid any cross-site-scripting issues.
The plugin doesn't detect any cycles within the formulae or aggregations. Say there is a formula like s = a + b and an aggregation like a = agg(s, x, y). This would lead to an infinit loop. Thus it's up to the developer to take care of this.
There is another plugin that does similar things. Maybe You should also have a look at that one.
The code is free to use it and comes without any warranty.
If You like it or if You have any suggestions to improve it or if You have found any bug (I guess there're still a lot) let me know.
This plugin is based on Calculator Plugin and adds some convience functions and methods.
This is a wrapper function for .addFormula(). It expects one or three parameters:
var mySet = { '#result1' : '{{#a}} + Math.sqrt({{square}})', '#result2' : '{{#b || 42}} * {{#c}}', 'square' : '{{left}} * {{right}}' }
var mySet = { 'resultxxx' : '{{opAxxx}} * {{opBxxx}}', 'squarexxx' : '{{leftxxx}} * {{rightxxx}}' } $.addFormulae(mySet, 'xxx', [ '1', '2', '3' ]);
is a short form for
$('result1').addFormula('{{opA1}} * {{opB1}}'); $('result2').addFormula('{{opA2}} * {{opB2}}'); $('result3').addFormula('{{opA3}} * {{opB3}}'); $('square1').addFormula('{{left1}} * {{right1}}'); $('square2').addFormula('{{left2}} * {{right2}}'); $('square3').addFormula('{{left3}} * {{right3}}');
This function installs aggregation methods like sum or avg. This function is called with a string array. Each string is the name of a well-known aggregation function. The following names could be set.
the aggregation is the sum over all elements.
the aggregation is the maximum of all elements.
the aggregation is the minimum of all elements.
the aggregation is the average of all elements.
var fcnSet = $.withAggregatorFunctions(['sum', 'avg']); $('#avgfield').avg('[name ^= "operands"]'); //OR: $('#avgfield').aggregate('[name ^= "operands"]', fcnSet.avg);
When adding formulae or aggregations to input fields (or elements) it is useful to know which input fields participate in the calculation of a result.
Say there are some fields (a, b, c) that are part of a sum aggregation (s). And the result itself is part of another calculation. When the form becomes a little more complex and the calculations are not that simple (as shown in the example above), than it could be hard to understand where the results come from and where do they go to then.
To help tracking which fields are a result's operands and what other fields or elements depend on these results, the Formula Tracker plugin was made.
The plugin does not have any (public) methods. Allthough it add the function .trackFormula() to $. This function is used when a formula or aggregation is added to a field/element.
To use this plugin it must be included.
<script type="text/javascript" src=".../jq-calc-formula-tracker.js"></script>
When double-clicking a field/element that is an operand or a result of a formula or an aggregation, a popup appears. When double-clicking again the popup disappears.
The popup consists of two or three columns.
field-op |
field-sum1
field-sum2
field-sum3
|
field-a
field-b
field-c
|
field-op |
field-sum1
field-sum2
|
When double-clicking one of the field names in the popup, this field's double-click trigger function is triggered. Thus that field's popup will be displayed. Or if it was already visible it will disappear.
By default the columns do not have any style. But they use css classes: