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/06/08 21:08:30 UTC

svn commit: r952772 - in /pivot/branches/1.5.x: tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ tutorials/src/org/apache/pivot/tutorials/webqueries/

Author: gbrown
Date: Tue Jun  8 19:08:29 2010
New Revision: 952772

URL: http://svn.apache.org/viewvc?rev=952772&view=rev
Log:
Complete QueryServlet tutorial.

Added:
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountValidator.java
      - copied, changed from r952659, pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/CurrencyValidator.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/DateBindMapping.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/IDBindMapping.java
Removed:
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/CurrencyValidator.java
Modified:
    pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/Expense.java
    pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ExpenseServlet.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountBindMapping.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpenseSheet.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/Expenses.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.json
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ResultItemRenderer.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/WebQueries.java
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expense_sheet.wtkx
    pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expenses_window.wtkx

Modified: pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/Expense.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/Expense.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/Expense.java (original)
+++ pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/Expense.java Tue Jun  8 19:08:29 2010
@@ -16,33 +16,38 @@
  */
 package org.apache.pivot.tutorials.webqueries.server;
 
-import org.apache.pivot.util.CalendarDate;
-
+/**
+ * Java Bean class representing an expense record.
+ */
 public class Expense {
-    private int id = -1;
-    private CalendarDate calendarDate = null;
+    private Integer id = -1;
+    private String date = null;
     private String type = null;
-    private double amount = 0;
+    private Double amount = 0d;
     private String description = null;
 
-    public int getID() {
+    public Integer getID() {
         return id;
     }
 
-    protected void setID(int id) {
+    public Integer getId() {
+        return getID();
+    }
+
+    public void setID(Integer id) {
         this.id = id;
     }
 
-    public CalendarDate getDate() {
-        return calendarDate;
+    public void setId(Integer id) {
+        setID(id);
     }
 
-    public void setDate(CalendarDate calendarDate) {
-        this.calendarDate = calendarDate;
+    public String getDate() {
+        return date;
     }
 
-    public final void setDate(String calendarDate) {
-        setDate(CalendarDate.decode(calendarDate));
+    public void setDate(String date) {
+        this.date = date;
     }
 
     public String getType() {
@@ -53,11 +58,11 @@ public class Expense {
         this.type = type;
     }
 
-    public double getAmount() {
+    public Double getAmount() {
         return amount;
     }
 
-    public void setAmount(double amount) {
+    public void setAmount(Double amount) {
         this.amount = amount;
     }
 

Modified: pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ExpenseServlet.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ExpenseServlet.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ExpenseServlet.java (original)
+++ pivot/branches/1.5.x/tutorials-server/src/org/apache/pivot/tutorials/webqueries/server/ExpenseServlet.java Tue Jun  8 19:08:29 2010
@@ -34,6 +34,9 @@ import org.apache.pivot.web.Query;
 import org.apache.pivot.web.QueryException;
 import org.apache.pivot.web.server.QueryServlet;
 
+/**
+ * Servlet that implements expense management web service.
+ */
 public class ExpenseServlet extends QueryServlet {
     private static final long serialVersionUID = 0;
 
@@ -57,8 +60,6 @@ public class ExpenseServlet extends Quer
 
         try {
             expenses = (List<Expense>)csvSerializer.readObject(inputStream);
-
-            // TODO Set a comparator so expenses are sorted by date, then type
         } catch (IOException exception) {
             throw new ServletException(exception);
         } catch (SerializationException exception) {
@@ -75,30 +76,30 @@ public class ExpenseServlet extends Quer
 
     @Override
     protected Object doGet(Path path) throws QueryException {
-        if (path.getLength() == 0) {
-            throw new QueryException(Query.Status.BAD_REQUEST);
-        }
+        Object value;
 
-        // Get the ID of the expense to retrieve from the path
-        int id = Integer.parseInt(path.get(0));
-
-        // Get the expense data from the map
-        Expense expense;
-        synchronized (this) {
-            expense = expenseMap.get(id);
-        }
-
-        if (expense == null) {
-            throw new QueryException(Query.Status.NOT_FOUND);
+        if (path.getLength() == 0) {
+            value = expenses;
+        } else {
+            // Get the ID of the expense to retrieve from the path
+            int id = Integer.parseInt(path.get(0));
+
+            // Get the expense data from the map
+            synchronized (this) {
+                value = expenseMap.get(id);
+            }
+
+            if (value == null) {
+                throw new QueryException(Query.Status.NOT_FOUND);
+            }
         }
 
-        return expense;
+        return value;
     }
 
     @Override
     protected URL doPost(Path path, Object value) throws QueryException {
-        if (path.getLength() > 0
-            || value == null) {
+        if (value == null) {
             throw new QueryException(Query.Status.BAD_REQUEST);
         }
 

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountBindMapping.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountBindMapping.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountBindMapping.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountBindMapping.java Tue Jun  8 19:08:29 2010
@@ -21,6 +21,9 @@ import java.text.NumberFormat;
 
 import org.apache.pivot.wtk.TextInput;
 
+/**
+ * Maps double values to strings and vice versa.
+ */
 public class AmountBindMapping implements TextInput.TextBindMapping {
     private NumberFormat amountFormat = new DecimalFormat();
 

Copied: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountValidator.java (from r952659, pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/CurrencyValidator.java)
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountValidator.java?p2=pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountValidator.java&p1=pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/CurrencyValidator.java&r1=952659&r2=952772&rev=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/CurrencyValidator.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/AmountValidator.java Tue Jun  8 19:08:29 2010
@@ -21,9 +21,9 @@ import java.math.BigDecimal;
 import org.apache.pivot.wtk.validation.Validator;
 
 /**
- * Validates that text represents a valid dollar value.
+ * Verifies that text represents a valid dollar value.
  */
-public class CurrencyValidator implements Validator {
+public class AmountValidator implements Validator {
     @Override
     public boolean isValid(String text) {
         boolean valid = true;

Added: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/DateBindMapping.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/DateBindMapping.java?rev=952772&view=auto
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/DateBindMapping.java (added)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/DateBindMapping.java Tue Jun  8 19:08:29 2010
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+package org.apache.pivot.tutorials.webqueries;
+
+import org.apache.pivot.collections.List;
+import org.apache.pivot.util.CalendarDate;
+import org.apache.pivot.wtk.Spinner;
+import org.apache.pivot.wtk.content.CalendarDateSpinnerData;
+
+/**
+ * Maps string values to calendar dates and vice versa.
+ */
+public class DateBindMapping implements Spinner.ItemBindMapping {
+    public int indexOf(List<?> spinnerData, Object value) {
+        CalendarDateSpinnerData calendarDateSpinnerData = (CalendarDateSpinnerData)spinnerData;
+        return calendarDateSpinnerData.indexOf(CalendarDate.decode((String)value));
+    }
+
+    public Object get(List<?> spinnerData, int index) {
+        return spinnerData.get(index).toString();
+    }
+}

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpenseSheet.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpenseSheet.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpenseSheet.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpenseSheet.java Tue Jun  8 19:08:29 2010
@@ -30,6 +30,9 @@ import org.apache.pivot.wtk.TextInput;
 import org.apache.pivot.wtkx.Bindable;
 import org.apache.pivot.wtkx.WTKX;
 
+/**
+ * Sheet that allows a user to add or edit an expense record.
+ */
 public class ExpenseSheet extends Sheet implements Bindable {
     @WTKX private Spinner dateSpinner = null;
     @WTKX private ListButton typeListButton = null;

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/Expenses.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/Expenses.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/Expenses.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/Expenses.java Tue Jun  8 19:08:29 2010
@@ -23,13 +23,47 @@ import org.apache.pivot.wtk.DesktopAppli
 import org.apache.pivot.wtk.Display;
 import org.apache.pivot.wtkx.WTKXSerializer;
 
+/**
+ * Query servlet tutorial client application.
+ */
 public class Expenses implements Application {
+    private String hostname = "localhost";
+    private int port = -1;
+    private boolean secure = false;
+
     private ExpensesWindow expensesWindow = null;
 
+    public static final String HOSTNAME_KEY = "hostname";
+    public static final String PORT_KEY = "port";
+    public static final String SECURE_KEY = "secure";
+
+    private static Expenses instance = null;
+
+    public Expenses() {
+        if (instance != null) {
+            throw new IllegalStateException("Another instance of " + Expenses.class.getName()
+                + " is already running.");
+        }
+
+        instance = this;
+    }
+
     @Override
     public void startup(Display display, Map<String, String> properties) throws Exception {
-        Resources resources = new Resources(ExpensesWindow.class.getName());
-        WTKXSerializer wtkxSerializer = new WTKXSerializer(resources);
+        // Get startup properties
+        if (properties.containsKey(HOSTNAME_KEY)) {
+            hostname = properties.get(HOSTNAME_KEY);
+        }
+
+        if (properties.containsKey(PORT_KEY)) {
+            port = Integer.parseInt(properties.get(PORT_KEY));
+        }
+
+        if (properties.containsKey(SECURE_KEY)) {
+            secure = Boolean.parseBoolean(properties.get(SECURE_KEY));
+        }
+
+        WTKXSerializer wtkxSerializer = new WTKXSerializer(new Resources(ExpensesWindow.class.getName()));
         expensesWindow = (ExpensesWindow)wtkxSerializer.readObject(this, "expenses_window.wtkx");
         expensesWindow.open(display);
     }
@@ -51,6 +85,22 @@ public class Expenses implements Applica
     public void resume() {
     }
 
+    public String getHostname() {
+        return hostname;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public boolean isSecure() {
+        return secure;
+    }
+
+    public static Expenses getInstance() {
+        return instance;
+    }
+
     public static void main(String[] args) {
         DesktopApplicationContext.main(Expenses.class, args);
     }

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.java Tue Jun  8 19:08:29 2010
@@ -17,24 +17,41 @@
 package org.apache.pivot.tutorials.webqueries;
 
 import java.io.IOException;
+import java.net.URL;
 
+import org.apache.pivot.collections.ArrayList;
 import org.apache.pivot.collections.HashMap;
 import org.apache.pivot.collections.List;
 import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.json.JSON;
 import org.apache.pivot.serialization.SerializationException;
 import org.apache.pivot.util.Resources;
+import org.apache.pivot.util.concurrent.Task;
+import org.apache.pivot.util.concurrent.TaskListener;
+import org.apache.pivot.web.DeleteQuery;
+import org.apache.pivot.web.GetQuery;
+import org.apache.pivot.web.PostQuery;
+import org.apache.pivot.web.PutQuery;
 import org.apache.pivot.wtk.Action;
+import org.apache.pivot.wtk.ActivityIndicator;
+import org.apache.pivot.wtk.BoxPane;
 import org.apache.pivot.wtk.Display;
+import org.apache.pivot.wtk.MessageType;
+import org.apache.pivot.wtk.Prompt;
 import org.apache.pivot.wtk.Sheet;
 import org.apache.pivot.wtk.SheetCloseListener;
 import org.apache.pivot.wtk.Span;
 import org.apache.pivot.wtk.TableView;
 import org.apache.pivot.wtk.TableViewSelectionListener;
+import org.apache.pivot.wtk.TaskAdapter;
 import org.apache.pivot.wtk.Window;
 import org.apache.pivot.wtkx.Bindable;
 import org.apache.pivot.wtkx.WTKX;
 import org.apache.pivot.wtkx.WTKXSerializer;
 
+/**
+ * Main expense management window.
+ */
 public class ExpensesWindow extends Window implements Bindable {
     private class RefreshExpenseListAction extends Action {
         @Override
@@ -78,8 +95,11 @@ public class ExpensesWindow extends Wind
     private DeleteSelectedExpenseAction deleteSelectedExpenseAction = new DeleteSelectedExpenseAction();
 
     @WTKX private TableView expenseTableView = null;
+    @WTKX private ActivityIndicator activityIndicator = null;
+    @WTKX private BoxPane activityIndicatorBoxPane = null;
 
     private ExpenseSheet expenseSheet = null;
+    private Prompt deleteConfirmationPrompt = null;
 
     public static final String REFRESH_EXPENSE_LIST_ACTION_ID = "refresh_expense_list";
     public static final String ADD_EXPENSE_ACTION_ID = "add_expense";
@@ -95,11 +115,11 @@ public class ExpensesWindow extends Wind
     }
 
     @Override
-    public void initialize(Resources resource) {
+    @SuppressWarnings("unchecked")
+    public void initialize(Resources resources) {
         // Load the add/edit sheet
         try {
-            Resources resources = new Resources(ExpenseSheet.class.getName());
-            WTKXSerializer wtkxSerializer = new WTKXSerializer(resources);
+            WTKXSerializer wtkxSerializer = new WTKXSerializer(new Resources(ExpenseSheet.class.getName()));
             expenseSheet = (ExpenseSheet)wtkxSerializer.readObject(this, "expense_sheet.wtkx");
         } catch (IOException exception) {
             throw new RuntimeException(exception);
@@ -107,6 +127,12 @@ public class ExpensesWindow extends Wind
             throw new RuntimeException(exception);
         }
 
+        // Create the delete confirmation prompt
+        ArrayList<String> options = new ArrayList(resources.getString("cancel"),
+            resources.getString("ok"));
+        deleteConfirmationPrompt = new Prompt(MessageType.QUESTION, resources.getString("confirmDelete"),
+            options);
+
         // Attach event listener(s)
         expenseTableView.getTableViewSelectionListeners().add(new TableViewSelectionListener.Adapter() {
             @Override
@@ -127,45 +153,181 @@ public class ExpensesWindow extends Wind
     }
 
     private void refreshExpenseList() {
-        // TODO Show activity indicator and load expenses
+        Expenses expensesApplication = Expenses.getInstance();
+        GetQuery expenseListQuery = new GetQuery(expensesApplication.getHostname(),
+            expensesApplication.getPort(), "/pivot-tutorials/expenses",
+            expensesApplication.isSecure());
+
+        activityIndicatorBoxPane.setVisible(true);
+        activityIndicator.setActive(true);
+
+        expenseListQuery.execute(new TaskAdapter<Object>(new TaskListener<Object>() {
+            @Override
+            public void taskExecuted(Task<Object> task) {
+                activityIndicatorBoxPane.setVisible(false);
+                activityIndicator.setActive(false);
+
+                List<?> expenseData = (List<?>)task.getResult();
+                expenseTableView.setTableData(expenseData);
+            }
+
+            @Override
+            public void executeFailed(Task<Object> task) {
+                activityIndicatorBoxPane.setVisible(false);
+                activityIndicator.setActive(false);
+
+                Prompt.prompt(MessageType.ERROR, task.getFault().getMessage(), ExpensesWindow.this);
+            }
+        }));
     }
 
     @SuppressWarnings("unchecked")
     private void addExpense() {
         expenseSheet.clear();
-        expenseSheet.open(ExpensesWindow.this, new SheetCloseListener() {
+        expenseSheet.open(this, new SheetCloseListener() {
             @Override
             public void sheetClosed(Sheet sheet) {
                 if (sheet.getResult()) {
-                    // Add result to table
-                    HashMap<String, Object> expense = new HashMap<String, Object>();
+                    // Get the expense data from the sheet
+                    final HashMap<String, Object> expense = new HashMap<String, Object>();
                     expenseSheet.store(expense);
 
-                    // TODO POST expense to server and then add to table
-
-                    List<Object> expenses = (List<Object>)expenseTableView.getTableData();
-                    expenses.add(expense);
+                    // POST expense to server and then add to table
+                    Expenses expensesApplication = Expenses.getInstance();
+                    PostQuery addExpenseQuery = new PostQuery(expensesApplication.getHostname(),
+                        expensesApplication.getPort(), "/pivot-tutorials/expenses",
+                        expensesApplication.isSecure());
+                    addExpenseQuery.setValue(expense);
+
+                    activityIndicatorBoxPane.setVisible(true);
+                    activityIndicator.setActive(true);
+
+                    addExpenseQuery.execute(new TaskAdapter<URL>(new TaskListener<URL>() {
+                        @Override
+                        public void taskExecuted(Task<URL> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            URL location = task.getResult();
+                            String file = location.getFile();
+                            int id = Integer.parseInt(file.substring(file.lastIndexOf('/') + 1));
+                            expense.put("id", id);
+
+                            List<Object> expenses = (List<Object>)expenseTableView.getTableData();
+                            expenses.add(expense);
+                        }
+
+                        @Override
+                        public void executeFailed(Task<URL> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            Prompt.prompt(MessageType.ERROR, task.getFault().getMessage(), ExpensesWindow.this);
+                        }
+                    }));
                 }
             }
         });
     }
 
+    @SuppressWarnings("unchecked")
     private void updateSelectedExpense() {
-        expenseSheet.load(expenseTableView.getSelectedRow());
-        expenseSheet.open(ExpensesWindow.this, new SheetCloseListener() {
+        Object expense = expenseTableView.getSelectedRow();
+        final int id = JSON.getInteger(expense, "id");
+
+        expenseSheet.load(expense);
+        expenseSheet.open(this, new SheetCloseListener() {
             @Override
             public void sheetClosed(Sheet sheet) {
                 if (sheet.getResult()) {
-                    HashMap<String, Object> expense = new HashMap<String, Object>();
+                    // Get the expense data from the sheet
+                    final HashMap<String, Object> expense = new HashMap<String, Object>();
                     expenseSheet.store(expense);
 
-                    // TODO PUT expense to server and then update table
+                    // PUT expense to server and then update table
+                    Expenses expensesApplication = Expenses.getInstance();
+                    PutQuery updateExpenseQuery = new PutQuery(expensesApplication.getHostname(),
+                        expensesApplication.getPort(), "/pivot-tutorials/expenses/" + JSON.get(expense, "id"),
+                        expensesApplication.isSecure());
+                    updateExpenseQuery.setValue(expense);
+
+                    activityIndicatorBoxPane.setVisible(true);
+                    activityIndicator.setActive(true);
+
+                    updateExpenseQuery.execute(new TaskAdapter<Boolean>(new TaskListener<Boolean>() {
+                        @Override
+                        public void taskExecuted(Task<Boolean> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            // Find matching row and update
+                            List<Object> expenses = (List<Object>)expenseTableView.getTableData();
+                            for (int i = 0, n = expenses.getLength(); i < n; i++) {
+                                if (JSON.get(expenses.get(i), "id").equals(id)) {
+                                    expenses.update(i, expense);
+                                    break;
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void executeFailed(Task<Boolean> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            Prompt.prompt(MessageType.ERROR, task.getFault().getMessage(), ExpensesWindow.this);
+                        }
+                    }));
                 }
             }
         });
     }
 
+    @SuppressWarnings("unchecked")
     private void deleteSelectedExpense() {
-        // TODO DELETE expense from server and then remove from table
+        Object expense = expenseTableView.getSelectedRow();
+        final int id = JSON.getInteger(expense, "id");
+
+        deleteConfirmationPrompt.open(this, new SheetCloseListener() {
+            @Override
+            public void sheetClosed(Sheet sheet) {
+                if (sheet.getResult()
+                    && ((Prompt)sheet).getSelectedOption() == 1) {
+                    // DELETE expense from server and then remove from table
+                    Expenses expensesApplication = Expenses.getInstance();
+                    DeleteQuery deleteExpenseQuery = new DeleteQuery(expensesApplication.getHostname(),
+                        expensesApplication.getPort(), "/pivot-tutorials/expenses/" + id,
+                        expensesApplication.isSecure());
+
+                    activityIndicatorBoxPane.setVisible(true);
+                    activityIndicator.setActive(true);
+
+                    deleteExpenseQuery.execute(new TaskAdapter<Void>(new TaskListener<Void>() {
+                        @Override
+                        public void taskExecuted(Task<Void> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            // Find matching row and remove
+                            List<Object> expenses = (List<Object>)expenseTableView.getTableData();
+                            for (int i = 0, n = expenses.getLength(); i < n; i++) {
+                                if (JSON.get(expenses.get(i), "id").equals(id)) {
+                                    expenses.remove(i, 1);
+                                    break;
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void executeFailed(Task<Void> task) {
+                            activityIndicatorBoxPane.setVisible(false);
+                            activityIndicator.setActive(false);
+
+                            Prompt.prompt(MessageType.ERROR, task.getFault().getMessage(), ExpensesWindow.this);
+                        }
+                    }));
+                }
+            }
+        });
     }
 }

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.json
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.json?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.json (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ExpensesWindow.json Tue Jun  8 19:08:29 2010
@@ -22,5 +22,8 @@
     amountFormat: "$#,##0.00",
     add: "Add",
     edit: "Edit",
-    delete: "Delete"
+    delete: "Delete",
+    confirmDelete: "Are you sure you want to delete this expense?",
+    cancel: "Cancel",
+    ok: "OK"
 }

Added: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/IDBindMapping.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/IDBindMapping.java?rev=952772&view=auto
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/IDBindMapping.java (added)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/IDBindMapping.java Tue Jun  8 19:08:29 2010
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+package org.apache.pivot.tutorials.webqueries;
+
+import org.apache.pivot.wtk.Label;
+
+/**
+ * Maps string values to ints and vice versa.
+ */
+public class IDBindMapping implements Label.TextBindMapping {
+    @Override
+    public String toString(Object value) {
+        return (value == null) ? null : value.toString();
+    }
+
+    @Override
+    public Object valueOf(String text) {
+        return (text == null) ? null : Integer.parseInt(text);
+    }
+}

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ResultItemRenderer.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ResultItemRenderer.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ResultItemRenderer.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/ResultItemRenderer.java Tue Jun  8 19:08:29 2010
@@ -27,6 +27,9 @@ import org.apache.pivot.wtk.Label;
 import org.apache.pivot.wtk.ListView;
 import org.apache.pivot.wtk.Orientation;
 
+/**
+ * List item renderer for query results.
+ */
 public class ResultItemRenderer extends BoxPane implements ListView.ItemRenderer {
     private Label titleLabel = new Label();
     private Label addressLabel = new Label();

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/WebQueries.java
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/WebQueries.java?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/WebQueries.java (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/WebQueries.java Tue Jun  8 19:08:29 2010
@@ -30,6 +30,9 @@ import org.apache.pivot.wtk.TaskAdapter;
 import org.apache.pivot.wtk.Window;
 import org.apache.pivot.wtkx.WTKXSerializer;
 
+/**
+ * Web query tutorial application.
+ */
 public class WebQueries implements Application {
     private Window window = null;
 

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expense_sheet.wtkx
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expense_sheet.wtkx?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expense_sheet.wtkx (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expense_sheet.wtkx Tue Jun  8 19:08:29 2010
@@ -40,12 +40,19 @@ limitations under the License.
                             <Form>
                                 <sections>
                                     <Form.Section>
-                                        <Label Form.label="%id" textKey="id"/>
+                                        <Label Form.label="%id" textKey="id">
+                                            <textBindMapping>
+                                                <webqueries:IDBindMapping/>
+                                            </textBindMapping>
+                                        </Label>
 
                                         <Spinner wtkx:id="dateSpinner" Form.label="%date" selectedItemKey="date">
                                             <spinnerData>
                                                 <content:CalendarDateSpinnerData/>
                                             </spinnerData>
+                                            <selectedItemBindMapping>
+                                                <webqueries:DateBindMapping/>
+                                            </selectedItemBindMapping>
                                         </Spinner>
 
                                         <ListButton wtkx:id="typeListButton" Form.label="%type" listData="@types.json"
@@ -54,7 +61,7 @@ limitations under the License.
                                         <TextInput wtkx:id="amountTextInput" Form.label="%amount" strictValidation="true"
                                             textKey="amount">
                                             <validator>
-                                                <webqueries:CurrencyValidator/>
+                                                <webqueries:AmountValidator/>
                                             </validator>
                                             <textBindMapping>
                                                 <webqueries:AmountBindMapping amountFormat="%amountFormat"/>

Modified: pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expenses_window.wtkx
URL: http://svn.apache.org/viewvc/pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expenses_window.wtkx?rev=952772&r1=952771&r2=952772&view=diff
==============================================================================
--- pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expenses_window.wtkx (original)
+++ pivot/branches/1.5.x/tutorials/src/org/apache/pivot/tutorials/webqueries/expenses_window.wtkx Tue Jun  8 19:08:29 2010
@@ -22,57 +22,67 @@ limitations under the License.
     xmlns:webqueries="org.apache.pivot.tutorials.webqueries"
     xmlns="org.apache.pivot.wtk">
     <content>
-        <TablePane styles="{verticalSpacing:4}">
-            <columns>
-                <TablePane.Column width="1*"/>
-            </columns>
-            <rows>
-                <TablePane.Row height="1*">
-                    <Border styles="{color:7}">
-                        <content>
-                            <ScrollPane horizontalScrollBarPolicy="fill_to_capacity">
-                                <view>
-                                    <TableView wtkx:id="expenseTableView" selectMode="single">
-                                        <columns>
-                                            <TableView.Column name="date" width="87" headerData="%date"/>
-                                            <TableView.Column name="type" width="125" headerData="%type"/>
-                                            <TableView.Column name="amount" width="75" headerData="%amount">
-                                                <cellRenderer>
-                                                    <content:TableViewNumberCellRenderer numberFormat="%amountFormat"/>
-                                                </cellRenderer>
-                                            </TableView.Column>
-                                            <TableView.Column name="description" width="150" headerData="%description"/>
-                                        </columns>
-                                    </TableView>
-                                </view>
-                                <columnHeader>
-                                    <TableViewHeader tableView="$expenseTableView" styles="{headersPressable:false}"/>
-                                </columnHeader>
-                            </ScrollPane>
-                        </content>
-                    </Border>
-                </TablePane.Row>
+        <StackPane>
+            <TablePane styles="{verticalSpacing:4}">
+                <columns>
+                    <TablePane.Column width="1*"/>
+                </columns>
+                <rows>
+                    <TablePane.Row height="1*">
+                        <Border styles="{color:7}">
+                            <content>
+                                <ScrollPane horizontalScrollBarPolicy="fill_to_capacity">
+                                    <view>
+                                        <TableView wtkx:id="expenseTableView" selectMode="single"
+                                            styles="{includeTrailingVerticalGridLine:true}">
+                                            <columns>
+                                                <TableView.Column name="date" width="87" headerData="%date"/>
+                                                <TableView.Column name="type" width="125" headerData="%type"/>
+                                                <TableView.Column name="amount" width="75" headerData="%amount">
+                                                    <cellRenderer>
+                                                        <content:TableViewNumberCellRenderer numberFormat="%amountFormat"/>
+                                                    </cellRenderer>
+                                                </TableView.Column>
+                                                <TableView.Column name="description" width="150" headerData="%description"/>
+                                            </columns>
+                                        </TableView>
+                                    </view>
+                                    <columnHeader>
+                                        <TableViewHeader tableView="$expenseTableView"
+                                            styles="{headersPressable:false, includeTrailingVerticalGridLine:true}"/>
+                                    </columnHeader>
+                                </ScrollPane>
+                            </content>
+                        </Border>
+                    </TablePane.Row>
 
-                <TablePane.Row height="-1">
-                    <BoxPane styles="{spacing:0, fill:true}">
-                        <PushButton action="add_expense" styles="{toolbar:true}">
-                            <buttonData>
-                                <content:ButtonData icon="@add.png" text="%add"/>
-                            </buttonData>
-                        </PushButton>
-                        <PushButton action="edit_selected_expense" styles="{toolbar:true}">
-                            <buttonData>
-                                <content:ButtonData icon="@pencil.png" text="%edit"/>
-                            </buttonData>
-                        </PushButton>
-                        <PushButton action="delete_selected_expense" styles="{toolbar:true}">
-                            <buttonData>
-                                <content:ButtonData icon="@delete.png" text="%delete"/>
-                            </buttonData>
-                        </PushButton>
-                    </BoxPane>
-                </TablePane.Row>
-            </rows>
-        </TablePane>
+                    <TablePane.Row height="-1">
+                        <BoxPane styles="{spacing:0, fill:true}">
+                            <PushButton action="add_expense" styles="{toolbar:true}">
+                                <buttonData>
+                                    <content:ButtonData icon="@add.png" text="%add"/>
+                                </buttonData>
+                            </PushButton>
+                            <PushButton action="edit_selected_expense" styles="{toolbar:true}">
+                                <buttonData>
+                                    <content:ButtonData icon="@pencil.png" text="%edit"/>
+                                </buttonData>
+                            </PushButton>
+                            <PushButton action="delete_selected_expense" styles="{toolbar:true}">
+                                <buttonData>
+                                    <content:ButtonData icon="@delete.png" text="%delete"/>
+                                </buttonData>
+                            </PushButton>
+                        </BoxPane>
+                    </TablePane.Row>
+                </rows>
+            </TablePane>
+
+            <BoxPane wtkx:id="activityIndicatorBoxPane" visible="false"
+                styles="{horizontalAlignment:'center', verticalAlignment:'center'}">
+                <ActivityIndicator wtkx:id="activityIndicator"
+                    preferredWidth="128" preferredHeight="128"/>
+            </BoxPane>
+        </StackPane>
     </content>
 </webqueries:ExpensesWindow>