You are viewing a plain text version of this content. The canonical link for it is here.
Posted to docs@cocoon.apache.org by da...@cocoon.zones.apache.org on 2006/05/27 00:16:16 UTC

[DAISY] Created: Calculated fields

A new document has been created.

http://cocoon.zones.apache.org/daisy/documentation/1161.html

Document ID: 1161
Branch: main
Language: default
Name: Calculated fields
Document Type: Cocoon Document
Created: 5/26/06 10:16:11 PM
Creator (owner): Simone Gianni
State: draft

Parts
=====

Content
-------
Mime type: text/xml
Size: 11518 bytes
Content:
<html>
<body>

<h1>Concept</h1>

<p>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.</p>

<h1>Definition</h1>

<p>Configuration example :</p>

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

<p>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.</p>

<p>As you can see, the special part is the <tt>fd:value</tt> element, it has the
following standard attributes :</p>

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

<p>No attribute is mandatory, while others may be required, depending on the
algorithm that is used.</p>

<p>The <tt>triggers</tt> 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.</p>

<p>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.</p>

<h1>Algorithms</h1>

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

<h2>Simple expression</h2>

<p>The simple expression algorithm is the default one, and is based on XReporter
expressions.</p>

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

<p>So you can simply write :</p>

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

<p>And you'll obtain that this field will be calculated when the <tt>items</tt>
or <tt>price</tt> fields change, multiplicating <tt>items</tt> with
<tt>price</tt>.</p>

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

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

<pre>&lt;fd:value eval="(subTotal / 100) * {/appliedVat}"/&gt;</pre>

<p>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.</p>

<p>You can also use all the XReporter functions, for example :</p>

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

<p>Will use the <tt>DayOfWeek</tt> function to display the day of week of the
date contained in the <tt>birthDay</tt> field.</p>

<p>There is one special function called <tt>Sum</tt> 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 :</p>

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

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

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

<h2>Repeated expression</h2>

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

<pre>&lt;fd:value type="repeated" repeat-on="..." eval="..." [initial-value="..."]/&gt;</pre>

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

<p>In java this would sound something similar to :</p>

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

<p>Or more formally :</p>

<ul>
<li>The <tt>initial-value</tt> is computed, <em>relative to the calculated
widget itself</em>, and stored in a variable called <tt>formulaResult</tt></li>
<li>For each widget specified in the <tt>repeat-on</tt> list :</li>
<ul>
<li>The value of the widget is assigned to a variable called
<tt>formulaCurrent</tt>.</li>
<li>The expression is evaluated <em>relative to the current widget</em></li>
<li>The result of the expression is assigned to the <tt>formulaResult</tt>
variable</li>
</ul>

<li><tt>formulaResult</tt> (so, the result of last evaluation of the expression)
is used for the calculated field value.</li>
</ul>

<p>For example, to calculate the <tt>grandTotal</tt> we could have used :</p>

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

<p>Obviously, we have the <tt>Sum</tt> function which is by far simpler, but the
repeated expression could be used for example :</p>

<p>To count all items with a price higher than 100:</p>

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

<p>(read : the result is the previous result plus one if current price value is
over 100, 0 if it's less than 100)</p>

<p>To obtain a sum of all movements in a report, wether they are positive or
negative amount movements:</p>

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

<p>To obtain a multiplicatory :</p>

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

<h2>Javascript</h2>

<p>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 :</p>

<pre>&lt;fd:value type="javascript" triggers="..."&gt;
  ... you javascript code here ...
&lt;/fd:value&gt;
</pre>

<p>In this case, no <tt>eval</tt> attribute is used, since javascript code is
written directly inside the fd:value element.</p>

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

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

<p>In the javascript snippet :</p>

<ul>
<li>you can access the Form object with the <tt>form</tt> variable.</li>
<li>you can access the parent widget with the <tt>parent</tt> variable (this is
useful in repeaters, cause the parent will be the current row).</li>
<li>you <em>must return the value</em> for the calculated field, not assign it
yourself.</li>
</ul>

<p>So, for example, to implement a discount policy for our cart we could write :
</p>

<pre>&lt;fd:calculatedfield id="discount" state="output"&gt;
  &lt;fd:label&gt;Total cost&lt;/fd:label&gt;
  &lt;fd:datatype base="double"&gt;
    &lt;fd:convertor type="formatting" variant="currency"/&gt;
  &lt;/fd:datatype&gt;
  &lt;fd:value type="javascript" triggers="cart/./items,cart/./price,discountCode"&gt;
     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;
     }
  &lt;/fd:value&gt;
&lt;/fd:calculatedfield&gt;
</pre>

<h2>Java</h2>

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

<pre>&lt;fd:value class="..." [triggers="..."]/&gt;</pre>

<p>A sample algorithm for the same discount policy could be the following :</p>

<pre>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);
        }
    }
}
</pre>

<p>Extending the <tt>AbstractBaseAlgorithm</tt> 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.</p>

</body>
</html>

Collections
===========
The document belongs to the following collections: documentation