blob: ad5b85a6103a866c0c82014e30c7b78c80534ad0 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed 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 com.google.gwt.bikeshed.sample.stocks.client;
import com.google.gwt.bikeshed.cells.client.FieldUpdater;
import com.google.gwt.bikeshed.list.client.PagingTableListView;
import com.google.gwt.bikeshed.list.shared.AsyncListModel;
import com.google.gwt.bikeshed.list.shared.ListListModel;
import com.google.gwt.bikeshed.list.shared.Range;
import com.google.gwt.bikeshed.list.shared.AsyncListModel.DataSource;
import com.google.gwt.bikeshed.sample.stocks.client.TransactionTreeViewModel.SectorListModel;
import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote;
import com.google.gwt.bikeshed.sample.stocks.shared.StockQuoteList;
import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest;
import com.google.gwt.bikeshed.sample.stocks.shared.StockResponse;
import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*/
public class StockSample implements EntryPoint, Updater {
/**
* The delay between updates in milliseconds.
*/
private static final int UPDATE_DELAY = 5000;
static String getFormattedPrice(int price) {
return NumberFormat.getCurrencyFormat("USD").format(price / 100.0);
}
/**
* The popup used to purchase stock.
*/
private BuySellPopup buySellPopup = new BuySellPopup();
private final Label cashLabel = new Label();
/**
* The {@link StockService} used to retrieve data.
*/
private final StockServiceAsync dataService = GWT.create(StockService.class);
private AsyncListModel<StockQuote> favoritesListModel;
private PagingTableListView<StockQuote> favoritesTable;
private final Label netWorthLabel = new Label();
private StockQueryWidget queryWidget;
private AsyncListModel<StockQuote> searchListModel;
private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker =
new HashMap<String, ListListModel<Transaction>>();
private ListListModel<Transaction> transactionListModel;
private List<Transaction> transactions;
private PagingTableListView<Transaction> transactionTable;
private SideBySideTreeView transactionTree;
/**
* The timer used to update the stock quotes.
*/
private Timer updateTimer = new Timer() {
@Override
public void run() {
update();
}
};
private TransactionTreeViewModel treeModel;
/**
* This is the entry point method.
*/
public void onModuleLoad() {
// Create the list models
searchListModel = new AsyncListModel<StockQuote>(
new DataSource<StockQuote>() {
public void requestData(AsyncListModel<StockQuote> listModel) {
update();
}
});
favoritesListModel = new AsyncListModel<StockQuote>(
new DataSource<StockQuote>() {
public void requestData(AsyncListModel<StockQuote> listModel) {
update();
}
});
transactionListModel = new ListListModel<Transaction>();
transactions = transactionListModel.getList();
// Create the favorites table.
favoritesTable = new PagingTableListView<StockQuote>(favoritesListModel, 10);
favoritesTable.addColumn(Columns.tickerColumn);
favoritesTable.addColumn(Columns.priceColumn);
favoritesTable.addColumn(Columns.sharesColumn);
favoritesTable.addColumn(Columns.dollarsColumn);
favoritesTable.addColumn(Columns.buyColumn);
favoritesTable.addColumn(Columns.sellColumn);
// Create the transactions table.
transactionTable = new PagingTableListView<Transaction>(transactionListModel, 10);
transactionTable.addColumn(Columns.transactionColumn);
transactionTable.addColumn(Columns.subtotalColumn);
treeModel = new TransactionTreeViewModel(this,
favoritesListModel, transactionListListModelsByTicker);
transactionTree = new SideBySideTreeView(treeModel, null, 200, 200);
Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean>() {
public void update(StockQuote object, Boolean value) {
setFavorite(object.getTicker(), value);
}
});
Columns.buyColumn.setFieldUpdater(new FieldUpdater<StockQuote, String>() {
public void update(StockQuote object, String value) {
buySellPopup.setStockQuote(object, true);
buySellPopup.center();
}
});
Columns.sellColumn.setFieldUpdater(new FieldUpdater<StockQuote, String>() {
public void update(StockQuote object, String value) {
buySellPopup.setStockQuote(object, false);
buySellPopup.center();
}
});
buySellPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
public void onClose(CloseEvent<PopupPanel> event) {
Transaction t = buySellPopup.getTransaction();
if (t != null) {
dataService.transact(t, new AsyncCallback<Transaction>() {
public void onFailure(Throwable caught) {
Window.alert("Error: " + caught.getMessage());
}
public void onSuccess(Transaction result) {
recordTransaction(result);
update();
}
/**
* Update transactions (list of all transactions),
* transactionTickers (set of all tickers involved in
* transactions), and transactionsByTicker (map from
* ticker to lists of transactions for that ticker).
*/
private void recordTransaction(Transaction result) {
transactions.add(0, result);
String ticker = result.getTicker();
// Update the next level of the transaction tree
// for the given ticker
ListListModel<Transaction> t =
transactionListListModelsByTicker.get(ticker);
if (t == null) {
t = new ListListModel<Transaction>();
transactionListListModelsByTicker.put(ticker, t);
}
t.getList().add(result);
}
});
}
}
});
// Add components to the page.
Widget headerWidget = new HTML("<b>Stock Game</b>");
HorizontalPanel cashPanel = new HorizontalPanel();
cashPanel.add(new HTML("<b>Available cash:</b>"));
cashPanel.add(cashLabel);
HorizontalPanel netWorthPanel = new HorizontalPanel();
netWorthPanel.add(new HTML("<b>Net worth:</b>"));
netWorthPanel.add(netWorthLabel);
VerticalPanel moneyPanel = new VerticalPanel();
moneyPanel.add(cashPanel);
moneyPanel.add(netWorthPanel);
DockLayoutPanel westPanel = new DockLayoutPanel(Unit.PCT);
westPanel.addNorth(moneyPanel, 25.0);
westPanel.add(new HTML("<table>" +
"<tr><td>Dan Rice</td><td>$10000</td></tr>" +
"<tr><td>Joel Webber</td><td>$10000</td></tr>" +
"<tr><td>John Labanca</td><td>$10000</td></tr>" +
"</table>"));
DockLayoutPanel layoutPanel = new DockLayoutPanel(Unit.EM);
layoutPanel.addNorth(headerWidget, 4.0);
layoutPanel.addWest(westPanel, 15.0);
layoutPanel.addNorth(transactionTree, 18.0);
DockLayoutPanel innerLayoutPanel = new DockLayoutPanel(Unit.PCT);
this.queryWidget = new StockQueryWidget(searchListModel, this);
innerLayoutPanel.addWest(queryWidget, 60.0);
DockLayoutPanel favoritesLayoutPanel = new DockLayoutPanel(Unit.EM);
favoritesLayoutPanel.addNorth(new Label("Portfolio / Favorites"), 2.0);
favoritesLayoutPanel.add(new ScrollPanel(favoritesTable));
innerLayoutPanel.add(favoritesLayoutPanel);
layoutPanel.add(innerLayoutPanel);
RootLayoutPanel.get().add(layoutPanel);
update();
}
/**
* Set or unset a ticker symbol as a 'favorite'.
*
* @param ticker the ticker symbol
* @param favorite if true, make the stock a favorite
*/
public void setFavorite(String ticker, boolean favorite) {
if (favorite) {
dataService.addFavorite(ticker, favoritesListModel.getRanges()[0],
new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
Window.alert("Error adding favorite");
}
public void onSuccess(StockResponse response) {
updateFavorites(response);
}
});
} else {
dataService.removeFavorite(ticker, favoritesListModel.getRanges()[0],
new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
Window.alert("Error removing favorite");
}
public void onSuccess(StockResponse response) {
updateFavorites(response);
}
});
}
}
/**
* Request data from the server using the last query string.
*/
public void update() {
if (queryWidget == null) {
return;
}
updateTimer.cancel();
Range[] searchRanges = searchListModel.getRanges();
Range[] favoritesRanges = favoritesListModel.getRanges();
SectorListModel sectorListModel = treeModel.getSectorListModel();
Range[] sectorRanges = sectorListModel == null ? null : sectorListModel.getRanges();
if (searchRanges == null || searchRanges.length == 0
|| favoritesRanges == null || favoritesRanges.length == 0) {
return;
}
String searchQuery = queryWidget.getSearchQuery();
StockRequest request = new StockRequest(searchQuery,
sectorListModel != null ? sectorListModel.getSector() : null,
searchRanges[0],
favoritesRanges[0],
sectorRanges != null && sectorRanges.length > 0 ? sectorRanges[0] : null);
dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
String message = caught.getMessage();
if (message.contains("Not logged in")) {
// Force the user to login.
Window.Location.reload();
} else {
Window.alert("ERROR: " + caught.getMessage());
updateTimer.schedule(UPDATE_DELAY);
}
}
public void onSuccess(StockResponse result) {
processStockResponse(result);
}
});
}
/**
* Process the {@link StockResponse} from the server.
*
* @param response the stock response
*/
private void processStockResponse(StockResponse response) {
// Update the search list.
StockQuoteList searchResults = response.getSearchResults();
searchListModel.updateDataSize(response.getNumSearchResults(), true);
searchListModel.updateViewData(searchResults.getStartIndex(),
searchResults.size(), searchResults);
// Update the favorites list.
updateFavorites(response);
updateSector(response);
// Update available cash.
int cash = response.getCash();
int netWorth = response.getNetWorth();
cashLabel.setText(getFormattedPrice(cash));
netWorthLabel.setText(getFormattedPrice(netWorth));
buySellPopup.setAvailableCash(cash);
// Restart the update timer.
updateTimer.schedule(UPDATE_DELAY);
}
private void updateFavorites(StockResponse response) {
// Update the favorites list.
StockQuoteList favorites = response.getFavorites();
favoritesListModel.updateDataSize(response.getNumFavorites(), true);
favoritesListModel.updateViewData(favorites.getStartIndex(),
favorites.size(), favorites);
}
private void updateSector(StockResponse response) {
// Update the sector list.
StockQuoteList sectorList = response.getSector();
if (sectorList != null) {
SectorListModel sectorListModel = treeModel.getSectorListModel();
sectorListModel.updateDataSize(response.getNumSector(), true);
sectorListModel.updateViewData(sectorList.getStartIndex(),
sectorList.size(), sectorList);
}
}
}