You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by gb...@apache.org on 2010/03/26 15:38:07 UTC

svn commit: r927875 - in /pivot/trunk/tutorials/www: stock-tracker.data-binding.xml stock-tracker.events.xml stock-tracker.localization.xml stock-tracker.web-queries.xml

Author: gbrown
Date: Fri Mar 26 14:38:06 2010
New Revision: 927875

URL: http://svn.apache.org/viewvc?rev=927875&view=rev
Log:
Finish Stock Tracker tutorial updates.

Modified:
    pivot/trunk/tutorials/www/stock-tracker.data-binding.xml
    pivot/trunk/tutorials/www/stock-tracker.events.xml
    pivot/trunk/tutorials/www/stock-tracker.localization.xml
    pivot/trunk/tutorials/www/stock-tracker.web-queries.xml

Modified: pivot/trunk/tutorials/www/stock-tracker.data-binding.xml
URL: http://svn.apache.org/viewvc/pivot/trunk/tutorials/www/stock-tracker.data-binding.xml?rev=927875&r1=927874&r2=927875&view=diff
==============================================================================
--- pivot/trunk/tutorials/www/stock-tracker.data-binding.xml (original)
+++ pivot/trunk/tutorials/www/stock-tracker.data-binding.xml Fri Mar 26 14:38:06 2010
@@ -43,28 +43,25 @@ limitations under the License.
         <p>
             The <tt>Dictionary</tt> argument passed to these methods provides the "bind context":
             a collection of name/value pairs representing the data to which the components are
-            bound. Each bindable component can be assigned a "bind key" that associates the
-            component with a value in the context. The default implementations do nothing;
-            components that support data binding override them to import data to the component
-            from the context during a load, and export data from the component to the context
-            during a store.
+            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.
         </p>
 
         <p>
-            Data binding is most often supported by components that accept and present user input
-            (such as a text field), but it can also be implemented by read-only components, such
-            as labels and progress meters. Most components allow a caller to bind only to a single
-            value (such as the "text" property of a label), though some support additional bindings
-            (for example, a checked list view that allows a caller to bind to both its items'
-            checked and selected states).
+            Many component 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>
 
         <p>
-            It is important to note that it is not possible to bind to a container directly.
-            However, containers may act as nested bind contexts - when a bind key is assigned to a
-            container, it is assumed to point to a nested <tt>Dictionary</tt> instance representing
-            a subordinate bind context. This enables complex JSON object graphs returned from a
-            web query to be seamlessly mapped to a set of data-bound components arranged in a
+            It is important to note that it is not possible to directly bind to a container
+            component. However, containers may act as nested bind contexts - when a bind key is
+            assigned to a container, it is assumed to point to a nested <tt>Dictionary</tt> instance
+            representing a subordinate bind context. This enables complex object graphs returned
+            from a web query to be seamlessly mapped to a set of data-bound components arranged in a
             non-trivial layout, for example.
         </p>
 
@@ -73,190 +70,138 @@ limitations under the License.
         <p>
             The Stock Tracker demo isn't quite that sophisticated. It uses a single, flat bind
             context 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 we
-            requested more data than we seemed to need from the GET query: the extra fields are
-            used to fill in the data in the detail form.
+            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 read-only labels - Stock Tracker uses a
-            one-way binding to map the retrieved quote data to the text property of each. We
-            specified the name of the key to use for each label in the
-            <tt>stocktracker.detail.wtkx</tt> file:
+            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[
-            ...
-            <Label Form.label="%value" textKey="value"/>
-            ...
+            <Form styles="{padding:0, fill:true, showFlagIcons:false, showFlagHighlight:false,
+                leftAlignLabels:true}">
+                <sections>
+                    <Form.Section>
+                        <wtkx:define>
+                            <stocktracker:ValueMapping wtkx:id="valueMapping"/>
+                            <stocktracker:ChangeMapping wtkx:id="changeMapping"/>
+                            <stocktracker:VolumeMapping wtkx:id="volumeMapping"/>
+                        </wtkx:define>
+
+                        <Label wtkx:id="valueLabel" Form.label="%value"
+                            textKey="value" textBindMapping="$valueMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                        <Label wtkx:id="changeLabel" Form.label="%change"
+                            textKey="change" textBindMapping="$valueMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                        <Label wtkx:id="openingValueLabel" Form.label="%openingValue"
+                            textKey="openingValue" textBindMapping="$valueMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                        <Label wtkx:id="highValueLabel" Form.label="%highValue"
+                            textKey="highValue" textBindMapping="$valueMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                        <Label wtkx:id="lowValueLabel" Form.label="%lowValue"
+                            textKey="lowValue" textBindMapping="$changeMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                        <Label wtkx:id="volumeLabel" Form.label="%volume"
+                            textKey="volume" textBindMapping="$volumeMapping"
+                            styles="{horizontalAlignment:'right'}"/>
+                    </Form.Section>
+                </sections>
+            </Form>
             ]]>
         </source>
 
         <p>
-            The actual binding occurs when the selection changes in the table view; as we saw in
-            the <a href="stock-tracker.events.html">Event Handling</a> section, the selection
+            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. The code for this method is as follows:
+            change event:
         </p>
 
         <source type="java">
             <![CDATA[
+            @SuppressWarnings("unchecked")
             private void refreshDetail() {
-                int firstSelectedIndex = stocksTableView.getFirstSelectedIndex();
-                removeSymbolsButton.setEnabled(firstSelectedIndex != -1);
-
                 StockQuote stockQuote = null;
-                Form.setFlag(detailChangeLabel, (Form.Flag)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);
-
-                        if (stockQuote.getChange() < 0) {
-                            Form.setFlag(detailChangeLabel, new Form.Flag(MessageType.ERROR));
-                        }
+                    } else {
+                        stockQuote = new StockQuote();
                     }
-                }
-
-                if (stockQuote == null) {
-                    detailRootPane.load(new HashMap<String, Object>());
                 } else {
-                    StockQuoteView stockQuoteView = new StockQuoteView(stockQuote);
-                    detailRootPane.load(stockQuoteView);
+                    stockQuote = new StockQuote();
                 }
-            }
 
+                detailPane.load(new BeanDictionary(stockQuote));
+            }
             ]]>
         </source>
 
         <p>
-            The method does the following:
-        </p>
-
-        <ul>
-            <li>
-                <p>
-                    Obtains the first selected index in the table (if more than one item is
-                    selected, we don't want to show anything in the detail)
-                </p>
-            </li>
-            <li>
-                <p>
-                    Clears the "flag" attribute from the detail change label (the <tt>Form</tt>
-                    container allows a caller to tag fields with a flag value, for use in data
-                    validation or just simple notification - here, a flag is used to indicate a
-                    negative change in the stock value)
-                </p>
-            </li>
-            <li>
-                <p>
-                    Gets a reference to the table view's data model and then to the data for the
-                    selected row
-                </p>
-            </li>
-            <li>
-                <p>
-                    If the change percentage is negative, shows a red "error" flag next to the
-                    detail label
-                </p>
-            </li>
-            <li>
-                <p>
-                    Wraps the row data in an instance of <tt>StockQuoteView</tt> and calls
-                    <tt>load()</tt>, populating the form with data from the selected quote
-                </p>
-            </li>
-        </ul>
-
-        <p>
-            <tt>StockQuoteView</tt> is a "decorator" (in the
-            <a href="http://en.wikipedia.org/wiki/Design_pattern_(computer_science)">design
-            pattern</a> sense, not the WTK sense) - it ensures that it is formatted and presented
-            in a readable manner in the detail view:
-        </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="java" location="org/apache/pivot/tutorials/stocktracker/StockQuoteView.java">
+        <source type="xml">
             <![CDATA[
-            package org.apache.pivot.tutorials.stocktracker;
+            <Label textKey="companyName" styles="{font:{size:12, bold:true}}"/>
+            ]]>
+        </source>
 
-            import java.text.DecimalFormat;
+        </p>
+            This example demostrates that a nested container does not automatically imply the
+            existence of a sub-context - sub-contexts are created only when a nested container is
+            assigned its own bind key. Because a bind key is not defined for it, the form simply
+            inherits the bind context that was passed to its parent, the root
+            <tt>&lt;BoxPane&gt;</tt> representing the detail pane.
+        </p>
 
-            import org.apache.pivot.beans.BeanDictionary;
+        <h2>Bind Mappings</h2>
 
-            public class StockQuoteView extends BeanDictionary {
-                private static final DecimalFormat valueFormat = new DecimalFormat("$0.00");
-                private static final DecimalFormat changeFormat = new DecimalFormat("+0.00;-0.00");
-                private static final DecimalFormat volumeFormat = new DecimalFormat();
+        <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>
 
-                public StockQuoteView(StockQuote stockQuote) {
-                    super(stockQuote);
-                }
+        <source type="java">
+            <![CDATA[
+            public class ValueMapping implements Label.TextBindMapping {
+                private static final DecimalFormat FORMAT = new DecimalFormat("$0.00");
 
                 @Override
-                public Object get(String key) {
-                    if (key == null) {
-                        throw new IllegalArgumentException("key is null.");
-                    }
-
-                    Object value = null;
-                    StockQuote stockQuote = (StockQuote)getBean();
-
-                    if (stockQuote == null) {
-                        value = "";
-                    } else {
-                        value = super.get(key);
-
-                        if (key.equals("value")
-                            || key.equals("openingValue")
-                            || key.equals("highValue")
-                            || key.equals("lowValue")) {
-                            try {
-                                Float floatValue = (Float)value;
-                                if (floatValue.isNaN()) {
-                                    value = "n/a";
-                                } else {
-                                    value = valueFormat.format(floatValue);
-                                }
-                            } catch(Exception exception) {
-                                value = "";
-                            }
-                        } else if (key.equals("change")) {
-                            try {
-                                value = changeFormat.format(value);
-                            } catch(Exception exception) {
-                                value = "";
-                            }
-                        } else if (key.equals("volume")) {
-                            try {
-                                value = volumeFormat.format(value);
-                            } catch(Exception exception) {
-                                value = "";
-                            }
-                        } else {
-                            if (value != null) {
-                                value = value.toString();
-                            }
-                        }
-                    }
+                public String toString(Object value) {
+                    return Float.isNaN((Float)value) ? null : FORMAT.format(value);
+                }
 
-                    return value;
+                @Override
+                public Object valueOf(String text) {
+                    throw new UnsupportedOperationException();
                 }
             }
             ]]>
         </source>
 
         <p>
-            Note that the <tt>load()</tt> method is actually called on the parent container of
-            the <tt>Form</tt>, rather than on the form itself. This is because we also want to
-            bind to the label that contains the company name, which is not a child of the
-            <tt>Form</tt>. A nested container does not automatically imply the existence of a
-            sub-context - sub-contexts are created only when a nested container is assigned its
-            own bind key. Because a bind key is not defined for it, the form simply inherits the
-            bind context that was passed to its parent.
+            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>

Modified: pivot/trunk/tutorials/www/stock-tracker.events.xml
URL: http://svn.apache.org/viewvc/pivot/trunk/tutorials/www/stock-tracker.events.xml?rev=927875&r1=927874&r2=927875&view=diff
==============================================================================
--- pivot/trunk/tutorials/www/stock-tracker.events.xml (original)
+++ pivot/trunk/tutorials/www/stock-tracker.events.xml Fri Mar 26 14:38:06 2010
@@ -68,29 +68,29 @@ limitations under the License.
                 <p>
                     Retrieves the "langauge" argument that was provided to the application context
                     when it was created - for desktop applications, this is a command-line
-                    argument; in the browser, it is passed via an applet parameter.
+                    argument; in the browser, it is passed via an applet parameter
                 </p>
             </li>
             <li>
                 <p>
                     Sets the default locale to an instance corresponding to the language
-                    argument.
+                    argument
                 </p>
             </li>
             <li>
                 <p>
-                    Loads the application resources.
+                    Loads the application resources
                 </p>
             </li>
             <li>
                 <p>
                     Creates an instance of <tt>WTKXSerializer</tt> and loads the WTKX source for
-                    the main window.
+                    the main window
                 </p>
             </li>
             <li>
                 <p>
-                    Opens the main window, causing the application to appear on the screen.
+                    Opens the main window, causing the application to appear on the screen
                 </p>
             </li>
         </ul>

Modified: pivot/trunk/tutorials/www/stock-tracker.localization.xml
URL: http://svn.apache.org/viewvc/pivot/trunk/tutorials/www/stock-tracker.localization.xml?rev=927875&r1=927874&r2=927875&view=diff
==============================================================================
--- pivot/trunk/tutorials/www/stock-tracker.localization.xml (original)
+++ pivot/trunk/tutorials/www/stock-tracker.localization.xml Fri Mar 26 14:38:06 2010
@@ -81,26 +81,39 @@ limitations under the License.
         <p class="caption">StockTracker_fr.json</p>
 
         <p>
-            As we've seen, references to these string resources can be embedded directly within a
-            WTKX file:
+            As noted in earlier sections, references to these string resources can be embedded
+            directly within a WTKX file:
         </p>
 
         <source type="xml">
             <![CDATA[
-            <Form styles="{fill:true}">
+            <Form styles="{padding:0, fill:true, showFlagIcons:false, showFlagHighlight:false,
+                leftAlignLabels:true}">
                 <sections>
                     <Form.Section>
-                        <Label Form.name="%value" textKey="value"
+                        <wtkx:define>
+                            <stocktracker:ValueMapping wtkx:id="valueMapping"/>
+                            <stocktracker:ChangeMapping wtkx:id="changeMapping"/>
+                            <stocktracker:VolumeMapping wtkx:id="volumeMapping"/>
+                        </wtkx:define>
+
+                        <Label wtkx:id="valueLabel" Form.label="%value"
+                            textKey="value" textBindMapping="$valueMapping"
                             styles="{horizontalAlignment:'right'}"/>
-                        <Label wtkx:id="changeLabel" Form.name="%change" textKey="change"
+                        <Label wtkx:id="changeLabel" Form.label="%change"
+                            textKey="change" textBindMapping="$valueMapping"
                             styles="{horizontalAlignment:'right'}"/>
-                        <Label Form.name="%openingValue" textKey="openingValue"
+                        <Label wtkx:id="openingValueLabel" Form.label="%openingValue"
+                            textKey="openingValue" textBindMapping="$valueMapping"
                             styles="{horizontalAlignment:'right'}"/>
-                        <Label Form.name="%highValue" textKey="highValue"
+                        <Label wtkx:id="highValueLabel" Form.label="%highValue"
+                            textKey="highValue" textBindMapping="$valueMapping"
                             styles="{horizontalAlignment:'right'}"/>
-                        <Label Form.name="%lowValue" textKey="lowValue"
+                        <Label wtkx:id="lowValueLabel" Form.label="%lowValue"
+                            textKey="lowValue" textBindMapping="$changeMapping"
                             styles="{horizontalAlignment:'right'}"/>
-                        <Label Form.name="%volume" textKey="volume"
+                        <Label wtkx:id="volumeLabel" Form.label="%volume"
+                            textKey="volume" textBindMapping="$volumeMapping"
                             styles="{horizontalAlignment:'right'}"/>
                     </Form.Section>
                 </sections>

Modified: pivot/trunk/tutorials/www/stock-tracker.web-queries.xml
URL: http://svn.apache.org/viewvc/pivot/trunk/tutorials/www/stock-tracker.web-queries.xml?rev=927875&r1=927874&r2=927875&view=diff
==============================================================================
--- pivot/trunk/tutorials/www/stock-tracker.web-queries.xml (original)
+++ pivot/trunk/tutorials/www/stock-tracker.web-queries.xml Fri Mar 26 14:38:06 2010
@@ -155,7 +155,7 @@ limitations under the License.
         </p>
 
         <p>
-            By default, <tt>CSVSerializer</tt> creates an instance of
+            <tt>CSVSerializer</tt> will, by default, create an instance of
             <tt>org.apache.pivot.collections.HashMap&lt;String, Object&gt;</tt> for each row it
             encounters in the CSV stream. However, a caller can specify the name of a different
             class using the <tt>setItemClass()</tt> method. This avoids the performance penalty of
@@ -313,7 +313,7 @@ limitations under the License.
             <tt>pivot.util.concurrent.Task</tt> class, of which <tt>GetQuery</tt> is a subclass.
             <tt>TaskAdapter</tt> is defined in the <tt>pivot.wtk</tt> package and ensures that the
             callback occurs on the UI thread (otherwise, the listener is called in the context of
-            the background thread).
+            the background thread, which can produce non-deterministic results).
         </p>
 
         <p>
@@ -337,23 +337,48 @@ limitations under the License.
 
         <source type="java">
             <![CDATA[
+            @Override
             public void taskExecuted(Task<Object> task) {
                 if (task == getQuery) {
-                    Sequence<Span> selectedRanges = stocksTableView.getSelectedRanges();
+                    List<Object> quotes = (List<Object>)task.getResult();
+
+                    // Preserve any existing sort and selection
+                    Sequence<?> selectedStocks = stocksTableView.getSelectedRows();
+
+                    List<Object> tableData = (List<Object>)stocksTableView.getTableData();
+                    Comparator<Object> comparator = tableData.getComparator();
+                    quotes.setComparator(comparator);
 
-                    List<StockQuote> quotes = (List<StockQuote>)task.getResult();
                     stocksTableView.setTableData(quotes);
 
-                    if (selectedRanges.getLength() > 0) {
-                        stocksTableView.setSelectedRanges(selectedRanges);
+                    if (selectedStocks.getLength() > 0) {
+                        // Select current indexes of selected stocks
+                        for (int i = 0, n = selectedStocks.getLength(); i < n; i++) {
+                            Object selectedStock = selectedStocks.get(i);
+
+                            int index = 0;
+                            for (Object stock : stocksTableView.getTableData()) {
+                                String symbol = JSON.getString(stock, "symbol");
+                                String selectedSymbol = JSON.getString(selectedStock, "symbol");
+
+                                if (symbol.equals(selectedSymbol)) {
+                                    stocksTableView.addSelectedIndex(index);
+                                    break;
+                                }
+
+                                index++;
+                            }
+                        }
                     } else {
                         if (quotes.getLength() > 0) {
                             stocksTableView.setSelectedIndex(0);
                         }
                     }
 
+                    refreshDetail();
+
                     DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
-                    DateFormat.MEDIUM, Locale.getDefault());
+                        DateFormat.MEDIUM, Locale.getDefault());
                     lastUpdateLabel.setText(dateFormat.format(new Date()));
 
                     getQuery = null;
@@ -368,14 +393,22 @@ limitations under the License.
         </p>
 
         <ul>
-            <li><p>Caches the current selection state of the quote table view</p></li>
+            <li>
+                <p>
+                    Caches the current sort and selection state of the quote table view
+                </p>
+            </li>
             <li>
                 <p>
                     Gets the result of the query and casts it to the appropriate type
-                    (<tt>List&lt;StockQuote&gt;</tt>)
+                    (<tt>List&lt;Object&gt;</tt>)
+                </p>
+            </li>
+            <li>
+                <p>
+                    Sets the list as the model data for the table view
                 </p>
             </li>
-            <li><p>Sets the list as the model data for the table view</p></li>
             <li>
                 <p>
                     Restores any selection state (which would have been lost when the model data