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><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></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><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></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><fd:value eval="(subTotal / 100) * {/appliedVat}"/></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><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>
</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><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></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><fd:value type="repeated" repeat-on="..." eval="..." [initial-value="..."]/></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 = <your formula here>;
}
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><fd:value type="repeated" eval="formulaResult + formulaCurrent" repeat-on="cart/./subTotal"/>
</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><fd:value type="repeated" repeat-on="/cart/./price" eval="formulaResult + If(formulaCurrent > 100, 1, 0)"/> </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><fd:value type="repeated" repeat-on="report/./amount" eval="formulaResult + Abs(formulaCurrent)"/></pre>
<p>To obtain a multiplicatory :</p>
<pre><fd:value type="repeated" repeat-on="data/./operand" eval="formulaResult * formulaCurrent" initial-value="1"/></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><fd:value type="javascript" triggers="...">
... you javascript code here ...
</fd:value>
</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><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>
</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><fd:value class="..." [triggers="..."]/></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