blob: e065483624b9de3c5ab7a0a19934b93345eefb21 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License,
Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<document id="stock-tracker.data-binding">
<properties>
<title>Data Binding</title>
</properties>
<body>
<p>
Data binding is the process of automatically mapping values between a set of user
interface elements and an internal data representation; for example, from a order entry
form to a collection of database fields or vice versa. Data binding can help simplify
development by eliminating some or all of the tedious boilerplate code that often goes
along with this type of programming.
</p>
<p>
In Pivot, data binding is controlled by the <tt>load()</tt> and <tt>store()</tt>
methods of the <tt>Component</tt> class:
</p>
<p>
<tt>public void load(Object context) {...}</tt><br/>
<tt>public void store(Object context) {...}</tt><br/>
</p>
<p>
The <tt>Object</tt> value passed to these methods provides the "bind context". It
may be either an implementation of the <tt>org.apache.pivot.collections.Dictionary</tt>
interface or a Java bean value that can be wrapped in an instance of
<tt>org.apache.pivot.beans.BeanAdapter</tt>, which implements <tt>Dictionary</tt>.
</p>
<p>
The bind context is essentially a collection of name/value pairs representing the data
to which the components will be bound. Each bindable property of a component can be
assigned a "bind key" that associates the property with a value in the context. Data is
imported from the context into the property during a load, and exported from the
property to the context during a store. Bind contexts may be flat or hierarchical;
JSON path syntax can be used to retrieve nested values (e.g. "foo.bar.baz" or
"foo['bar'].baz").
</p>
<p>
Many Pivot components support data binding including text inputs, checkboxes and radio
buttons, list views, and table views, among others. Some components support binding to
multiple properties; for example, a caller can bind to both the list data as well as the
selection state of a <tt>ListView</tt> component.
</p>
<h3>Data Binding in Stock Tracker</h3>
<p>
The Stock Tracker demo uses data binding to populate the fields in the quote detail
view. The bind context is actually the row data retrieved from the web query for the
selected stock. This is why the application requests more data than it seems to need
from the GET query: the extra fields are used to fill in the data in the detail form.
</p>
<p>
The bound components, in this case, are labels - Stock Tracker maps values from the
retrieved quote data to the text property of each. The name of the key to use for each
label is specified via the "textKey" property:
</p>
<source type="xml">
<![CDATA[
<Form styles="{padding:0, fill:true, showFlagIcons:false, showFlagHighlight:false,
leftAlignLabels:true}">
<Form.Section>
<bxml:define>
<stocktracker:ValueMapping bxml:id="valueMapping"/>
<stocktracker:ChangeMapping bxml:id="changeMapping"/>
<stocktracker:VolumeMapping bxml:id="volumeMapping"/>
</bxml:define>
<Label bxml:id="valueLabel" Form.label="%value"
textKey="value" textBindMapping="$valueMapping"
styles="{horizontalAlignment:'right'}"/>
<Label bxml:id="changeLabel" Form.label="%change"
textKey="change" textBindMapping="$changeMapping"
styles="{horizontalAlignment:'right'}"/>
<Label bxml:id="openingValueLabel" Form.label="%openingValue"
textKey="openingValue" textBindMapping="$valueMapping"
styles="{horizontalAlignment:'right'}"/>
<Label bxml:id="highValueLabel" Form.label="%highValue"
textKey="highValue" textBindMapping="$valueMapping"
styles="{horizontalAlignment:'right'}"/>
<Label bxml:id="lowValueLabel" Form.label="%lowValue"
textKey="lowValue" textBindMapping="$valueMapping"
styles="{horizontalAlignment:'right'}"/>
<Label bxml:id="volumeLabel" Form.label="%volume"
textKey="volume" textBindMapping="$volumeMapping"
styles="{horizontalAlignment:'right'}"/>
</Form.Section>
</Form>
]]>
</source>
<p>
The actual binding occurs when the selection changes in the table view; the selection
change handler calls the <tt>refreshDetail()</tt> method in response to a selection
change event:
</p>
<source type="java">
<![CDATA[
@SuppressWarnings("unchecked")
private void refreshDetail() {
StockQuote stockQuote = null;
int firstSelectedIndex = stocksTableView.getFirstSelectedIndex();
if (firstSelectedIndex != -1) {
int lastSelectedIndex = stocksTableView.getLastSelectedIndex();
if (firstSelectedIndex == lastSelectedIndex) {
List<StockQuote> tableData = (List<StockQuote>)stocksTableView.getTableData();
stockQuote = tableData.get(firstSelectedIndex);
} else {
stockQuote = new StockQuote();
}
} else {
stockQuote = new StockQuote();
}
detailPane.load(new BeanAdapter(stockQuote));
}
]]>
</source>
<p>
Note that the <tt>load()</tt> method is actually called on the detail pane itself
rather than on the parent container of the detail labels (an instance of <tt>Form</tt>).
This is because the application also needs to bind to the label that contains the
company name, which is not a child of the <tt>Form</tt>:
</p>
<source type="xml">
<![CDATA[
<Label textKey="companyName" styles="{font:{size:12, bold:true}}"/>
]]>
</source>
<h3>Bind Mappings</h3>
<p>
Also note the use of the "textBindMapping" attributes. Bind mappings allow a caller
to modify the data during the bind process. Incoming data can be converted before it
is assigned to a property, and outgoing data can be converted before it is stored in
the bind context. For example, the <tt>ValueMapping</tt> class formats incoming data
as a currency value in U.S. dollars:
</p>
<source type="java">
<![CDATA[
public class ValueMapping implements Label.TextBindMapping {
private static final DecimalFormat FORMAT = new DecimalFormat("$0.00");
@Override
public String toString(Object value) {
return Float.isNaN((Float)value) ? null : FORMAT.format(value);
}
@Override
public Object valueOf(String text) {
throw new UnsupportedOperationException();
}
}
]]>
</source>
<p>
The <tt>toString() method is called to perform the translation during a <tt>load()</tt>
operation</tt>. The <tt>valueOf()</tt> method would be called during <tt>store()</tt>,
but throws <tt>UnsupportedOperationException</tt> because <tt>store()</tt> is never
called by the Stock Tracker application.
</p>
</body>
</document>