Apache » Cocoon »

The Apache Cocoon Project

# Cocoon Forms 1.0

- Cocoon 2.2
- Core 2.2
- Blocks 2.2
- Maven Plugins 2.2
- Subprojects

# Calculated fields

# Concept

A calculated field is a widget that calculated it's own value using an algorithm and values of other widgets. For example, if you are creating a form for a shopping cart, you'll probably have a repeater with items your customer is buying, each one with it's price, it's quantity, a subtotal and a grand total at the end of it. That total and subtotal fields are a typical use case of calculated fields.

# Definition

Configuration example :

    <fd:calculatedfield id="..." required="true|false">
      <fd:label>...</fd:label>
      <fd:datatype base="...">
        <fd:convertor type="..."/>
      </fd:datatype>
      <fd:value [type="..." eval="..." triggers="..."]/>
    </fd:calculatedfield>

It also supports all the other configuration options of a common field, like listeners, attributes etc.. Nothing special is needed in form template, binding or XSLTs, just use a calculated field as you would use a common field.

As you can see, the special part is the `fd:value` element, it has the following standard attributes :

- `type` indicates the type of algorith to use, currently `simple`, `repeated`, `javascript` and `java` are provided. If not specified, the simple expression algorithm will be used.
- `eval` contains the expression to use.
- `triggers` contains a comma separated list of widgets that will trigger a recalculation when their value changes.

No attribute is mandatory, while others may be required, depending on the algorithm that is used.The `triggers` attribute is not mandatory because many algorithms are able to understand which widget should trigger a recalculation parsing their expression, but actually the user can always specify this list manually to override the algorithm assumption.The calculations are done at server side. Whenever a trigger field gets its value changed by user interaction, the form is submitted, recalculated server side, and redisplayed to the user. Thus the usage of AJAX is highly recommended in a form that uses many calculated fields.

# Algorithms

Four algorithms are provided in cocoon : simple expression (default), repeated expression, javascript and java.

## Simple expression

The simple expression algorithm is the default one, and is based on XReporter expressions.

Only the `eval` attribute is required, because this is the default algorithm (so no `type` needed) and it can guess its triggers parsing the expression.

So you can simply write :

    <fd:calculatedfield id="subTotal" state="output">
      <fd:label>Subtotal</fd:label>
      <fd:datatype base="double">
        <fd:convertor type="formatting" variant="currency"/>
      </fd:datatype>
      <fd:value eval="items * price"/>
    </fd:calculatedfield>

And you'll obtain that this field will be calculated when the `items` or `price` fields change, multiplicating `items` with `price`.

As in `asserts` and other validations that uses XReporter expressions, widget names are evaluated relative to the container, so `items` and `price` are siblings of `subTotal` in the form definition tree, for example contained in the same repeater.

You can access widgets in other part of the form as well, using a path-link syntax and placing it in brackets, for example :

    <fd:value eval="(subTotal / 100) * {/appliedVat}"/>

Could be the value of a calculated field for VAT, that uses the VAT value (which could change for foreign countries) taken from a field outside the repeater.

You can also use all the XReporter functions, for example :

    <fd:calculatedfield id="dayOfweek" state="output">
      <fd:value eval="DayOfWeek(birthDay)"/>
      <fd:selection-list>
        <fd:item value="0"><fd:label>Sunday</fd:label></fd:item>
        ....
      </fd:selection-list>
    </fd:calculatedfield>

Will use the `DayOfWeek` function to display the day of week of the date contained in the `birthDay` field.

There is one special function called `Sum` that operates on a list of widgets instead that just one like other functions. This function uses a "special" syntax for determining which widgets to use :

    <fd:calculatedfield id="itemsTotal" state="output">
      <fd:label>Items in your cart</fd:label>
      <fd:datatype base="integer"/>
      <fd:value eval="Sum({cart/./items})"/>
    </fd:calculatedfield>

    <fd:calculatedfield id="grandTotal" state="output">
      <fd:label>Total cost</fd:label>
      <fd:datatype base="double">
        <fd:convertor type="formatting" variant="currency"/>
      </fd:datatype>
      <fd:value eval="Sum({cart/./subTotal})"/>
    </fd:calculatedfield>

"cart" is the name of a repeater, and "/./" means "every row". So `Sum({cart/./items})` simply means "Sum the value of the `items` widget in every row of the `cart` repeater". As you can see, the `grandTotal` field is a sum of calculated fields.

## Repeated expression

The repeated expression is similar to a simple expression, but is repeated for each indicated widget. The syntax is as follows :

    <fd:value type="repeated" repeat-on="..." eval="..." [initial-value="..."]/>

- type is used to indicate which algorithm we are using, in this case "`repeated`"
- repeat-on contains a list of widgets (comma separated, and/or with the /./ notation) we are going to iterate on
- eval contains the expression to repeat
- initial-value optionally contains and expression that will be used as an initial value for the computation.

In java this would sound something similar to :

    Iterator iter = widgetsList.iterator();
    int formulaResult = initialValue;
    while (iter.hasNext()) {
      formulaCurrent = ((Widget)iter.next()).getValue();
      formulaResult = <your formula here>;
    }
    this.setValue(formulaResult);

Or more formally :

- The `initial-value` is computed, relative to the calculated widget itself, and stored in a variable called `formulaResult`
- For each widget specified in the `repeat-on` list :

- The value of the widget is assigned to a variable called `formulaCurrent`.
- The expression is evaluated relative to the current widget
- The result of the expression is assigned to the `formulaResult` variable

- `formulaResult` (so, the result of last evaluation of the expression) is used for the calculated field value.

For example, to calculate the `grandTotal` we could have used :

    <fd:value type="repeated" eval="formulaResult + formulaCurrent" repeat-on="cart/./subTotal"/>

Obviously, we have the `Sum` function which is by far simpler, but the repeated expression could be used for example :To count all items with a price higher than 100:

    <fd:value type="repeated" repeat-on="/cart/./price" eval="formulaResult + If(formulaCurrent > 100, 1, 0)"/>

(read : the result is the previous result plus one if current price value is over 100, 0 if it's less than 100)To obtain a sum of all movements in a report, wether they are positive or negative amount movements:

    <fd:value type="repeated" repeat-on="report/./amount" eval="formulaResult + Abs(formulaCurrent)"/>

To obtain a multiplicatory :

    <fd:value type="repeated" repeat-on="data/./operand" eval="formulaResult * formulaCurrent" initial-value="1"/>

## Javascript

There are obviously situations where the simple or repeated expressions are not enought, so you can use javascript to quickly write your own calculated field algorithm :

    <fd:value type="javascript" triggers="...">
      ... you javascript code here ...
    </fd:value>

In this case, no `eval` attribute is used, since javascript code is written directly inside the fd:value element.

As opposite to simple and repeated expressions, you have to specify the `triggers`. Triggers are widgets that, when modified, trigger a recalculation of this calculated field.

The `triggers` attribute is a comma separated list of widgets, eventually their full path and/or with the /./ notation.

In the javascript snippet :

- you can access the Form object with the `form` variable.
- you can access the parent widget with the `parent` variable (this is useful in repeaters, cause the parent will be the current row).
- you must return the value for the calculated field, not assign it yourself.

So, for example, to implement a discount policy for our cart we could write :

    <fd:calculatedfield id="discount" state="output">
      <fd:label>Total cost</fd:label>
      <fd:datatype base="double">
        <fd:convertor type="formatting" variant="currency"/>
      </fd:datatype>
      <fd:value type="javascript" triggers="cart/./items,cart/./price,discountCode">
         var code = form.lookupWidget('discountCode').getValue();
         var verifier = new Packages.com.mycompany.discounts.Verifier();
         if (!verifier.isValidDiscountCode(code)) {
           return 0;
         } else {
           var discountAmount = 0;
           ...
           return discountAmount;
         }
      </fd:value>
    </fd:calculatedfield>

## Java

An algorithm can also be implemented as a java class. In this case the class must either implement the `CalculatedFieldAlgorithm` interface, or subclass the `AbstractBaseAlgorithm` class. The definition syntax is :

    <fd:value class="..." [triggers="..."]/>

A sample algorithm for the same discount policy could be the following :

    public class DiscountAlgorithm extends AbstractBaseAlgorithm {

        // This method is called to check if this algorithm is suitable
        // for the field it has been assigned to.
        public boolean isSuitableFor(Datatype dataType) {
            return dataType.getTypeClass().isAssignableFrom(Double.class);
        }

        // This method is actually called to perform the calculation,
        // it must return the value for the widget.
        public Object calculate(Form form, Widget parent, Datatype datatype) {
            String discountCode = form.lookupWidget("discountCode").getValue();
            Verifier verifier = new Verifier();
            if (!verifier.isValidDiscountCode(code)) {
                return new Double(0);
            } else {
                double discountAmount = 0;
                ...
                return new Double(discountAmount);
            }
        }
    }

Extending the `AbstractBaseAlgorithm` class is the preferred way of implementing a custom algorithm, because it would benefit from some standard features (triggers list for example) and from a better insulation from future calculated fields improvements.

Errors and Improvements? If you see any errors or potential improvements in this document please help us: View, Edit or comment on the latest development version (registration required).