Separates the stock sample into "desktop" and "mobile" versions. Makes some
other not-quite-perfect changes to sxs-tree layout.

Review at http://gwt-code-reviews.appspot.com/276801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7795 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
index 114ebfe..04f0eb0 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
@@ -22,6 +22,7 @@
 import com.google.gwt.dom.client.Style;
 import com.google.gwt.dom.client.Style.Position;
 import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.RequiresResize;
 
 import java.util.List;
 
@@ -32,8 +33,6 @@
  */
 public class SideBySideTreeNodeView<T> extends TreeNodeView<T> {
 
-  private int columnHeight;
-
   private int columnWidth;
 
   private final int imageLeft;
@@ -53,13 +52,12 @@
    */
   SideBySideTreeNodeView(final TreeView tree, final SideBySideTreeNodeView<?> parent,
       NodeInfo<T> parentNodeInfo, Element elem, T value, int level, String path,
-      int columnWidth, int columnHeight) {
+      int columnWidth) {
     super(tree, parent, parentNodeInfo, value);
     this.imageLeft = columnWidth - 16 - tree.getImageWidth();
     this.level = level;
     this.path = path;
     this.columnWidth = columnWidth;
-    this.columnHeight = columnHeight;
 
     setElement(elem);
   }
@@ -68,7 +66,7 @@
   protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
       Element childElem, C childValue, Void viewData, int idx) {
     return new SideBySideTreeNodeView<C>(getTree(), this, nodeInfo, childElem,
-        childValue, level + 1, path + "-" + idx, columnWidth, columnHeight);
+        childValue, level + 1, path + "-" + idx, columnWidth);
   }
 
   @Override
@@ -119,6 +117,13 @@
       setChildContainer(animFrame.appendChild(Document.get().createDivElement()));
     }
 
+    // TODO(jgw): Kind of a hack. We should probably be propagating onResize()
+    // down from the TreeView, but this is simpler for the moment.
+    TreeView tree = getTree();
+    if (tree instanceof RequiresResize) {
+      ((RequiresResize) tree).onResize();
+    }
+
     return getChildContainer();
   }
 
@@ -195,7 +200,6 @@
       style.setTop(0, Unit.PX);
       style.setLeft(level * columnWidth, Unit.PX);
       style.setWidth(columnWidth, Unit.PX);
-      style.setHeight(columnHeight, Unit.PX);
 
       childCount++;
     }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
index 57f42e8..0a6616c 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
@@ -15,8 +15,11 @@
  */
 package com.google.gwt.bikeshed.tree.client;
 
+import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
 import com.google.gwt.dom.client.Style;
 import com.google.gwt.dom.client.Style.Position;
 import com.google.gwt.dom.client.Style.Unit;
@@ -28,8 +31,6 @@
  */
 public class SideBySideTreeView extends TreeView {
 
-  protected int columnHeight = 200;
-
   protected int columnWidth = 100;
 
   /**
@@ -39,21 +40,18 @@
    * @param viewModel the {@link TreeViewModel} that backs the tree
    * @param rootValue the hidden root value of the tree
    * @param columnWidth
-   * @param columnHeight
    */
   public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue,
-      int columnWidth, int columnHeight) {
+      int columnWidth) {
     super(viewModel);
 
     this.columnWidth = columnWidth;
-    this.columnHeight = columnHeight;
 
     Element rootElement = Document.get().createDivElement();
     rootElement.setClassName("gwt-sstree");
     Style style = rootElement.getStyle();
     style.setPosition(Position.RELATIVE);
     style.setWidth(columnWidth, Unit.PX);
-    style.setHeight(columnHeight, Unit.PX);
     setElement(rootElement);
 
     // Add event handlers.
@@ -61,7 +59,7 @@
 
     // Associate a view with the item.
     TreeNodeView<T> root = new SideBySideTreeNodeView<T>(this, null, null,
-        rootElement, rootValue, 0, "gwt-sstree", columnWidth, columnHeight);
+        rootElement, rootValue, 0, "gwt-sstree", columnWidth);
     setRootNode(root);
     root.setState(true);
   }
@@ -106,4 +104,27 @@
         break;
     }
   }
+
+  public void onResize() {
+    if (!isAttached()) {
+      return;
+    }
+
+    int height = getElement().getOffsetHeight();
+    NodeList<Node> children = getElement().getChildNodes();
+    for (int i = 0; i < children.getLength(); ++i) {
+      Element child = children.getItem(i).cast();
+      child.getStyle().setHeight(height, Unit.PX);
+    }
+  }
+
+  @Override
+  protected void onLoad() {
+    // TODO(jgw): This is a total hack.
+    Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
+      public void execute() {
+        onResize();
+      }
+    });
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StockSample.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksCommon.gwt.xml
similarity index 60%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StockSample.gwt.xml
rename to bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksCommon.gwt.xml
index c3c3ef4..c69470f 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StockSample.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksCommon.gwt.xml
@@ -1,16 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
-<module rename-to='stocks'>
-  <!-- Inherit the core Web Toolkit stuff.                        -->
+<module>
   <inherits name='com.google.gwt.user.User'/>
   <inherits name='com.google.gwt.regexp.RegExp'/>
   <inherits name='com.google.gwt.bikeshed.list.List'/>
   <inherits name='com.google.gwt.bikeshed.tree.Tree'/>
 
-  <!-- Specify the app entry point class.                         -->
-  <entry-point class='com.google.gwt.sample.bikeshed.stocks.client.StockSample'/>
-
-  <!-- Specify the paths for translatable code                    -->
   <source path='client'/>
   <source path='shared'/>
 </module>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksDesktop.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksDesktop.gwt.xml
new file mode 100644
index 0000000..6977c58
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksDesktop.gwt.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
+<module rename-to='stocksdesktop'>
+  <inherits name='com.google.gwt.sample.bikeshed.stocks.StocksCommon'/>
+  <entry-point class='com.google.gwt.sample.bikeshed.stocks.client.StocksDesktop'/>
+</module>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksMobile.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksMobile.gwt.xml
new file mode 100644
index 0000000..c6cfbcd
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/StocksMobile.gwt.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
+<module rename-to='stocksmobile'>
+  <inherits name='com.google.gwt.sample.bikeshed.stocks.StocksCommon'/>
+  <entry-point class='com.google.gwt.sample.bikeshed.stocks.client.StocksMobile'/>
+</module>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
index cc8d61e..6e9b848 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
@@ -110,7 +110,7 @@
     protected String getValue(Transaction object) {
       int price = object.getActualPrice() * object.getQuantity();
       return (object.isBuy() ? " (" : " ")
-          + StockSample.getFormattedPrice(price) + (object.isBuy() ? ")" : "");
+          + StocksDesktop.getFormattedPrice(price) + (object.isBuy() ? ")" : "");
     }
   };
 
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
index 4c18352..ac55f44 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
@@ -48,9 +48,9 @@
       sb.append("<b>Name: </b>");
       sb.append(value.getDisplayName());
       sb.append("<br><b>Net Worth: </b>");
-      sb.append(StockSample.getFormattedPrice(value.getNetWorth()));
+      sb.append(StocksDesktop.getFormattedPrice(value.getNetWorth()));
       sb.append("<br><b>Cash: </b>");
-      sb.append(StockSample.getFormattedPrice(value.getCash()));
+      sb.append(StocksDesktop.getFormattedPrice(value.getCash()));
       
       List<String> status = value.getStatus();
       if (status != null) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
similarity index 98%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.java
rename to bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
index 2aad2f7..4071b5b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
@@ -51,9 +51,9 @@
 /**
  * Entry point classes define <code>onModuleLoad()</code>.
  */
-public class StockSample implements EntryPoint, Updater {
+public class StocksDesktop implements EntryPoint, Updater {
 
-  interface Binder extends UiBinder<Widget, StockSample> { }
+  interface Binder extends UiBinder<Widget, StocksDesktop> { }
 
   private static final Binder binder = GWT.create(Binder.class);
 
@@ -333,7 +333,7 @@
 
   @UiFactory
   SideBySideTreeView createTransactionTree() {
-    return new SideBySideTreeView(treeModel, null, 200, 200);
+    return new SideBySideTreeView(treeModel, null, 200);
   }
 
   // Hack - walk the transaction tree to find the current viewed sector
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
similarity index 90%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.ui.xml
rename to bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
index 77670d2..9742448 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockSample.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
@@ -31,9 +31,11 @@
     </g:west>
 
     <g:north size="18">
-      <g:ScrollPanel styleName='{common.bg}'>
-        <t:SideBySideTreeView ui:field='transactionTree'/>
-      </g:ScrollPanel>
+      <g:LayoutPanel styleName='{common.bg}'>
+        <g:layer>
+          <t:SideBySideTreeView ui:field='transactionTree'/>
+        </g:layer>
+      </g:LayoutPanel>
     </g:north>
 
     <g:center>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java
new file mode 100644
index 0000000..cca8651
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java
@@ -0,0 +1,146 @@
+package com.google.gwt.sample.bikeshed.stocks.client;
+
+import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.client.TextHeader;
+import com.google.gwt.bikeshed.list.shared.AsyncListModel;
+import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
+import com.google.gwt.sample.bikeshed.stocks.shared.StockQuoteList;
+import com.google.gwt.sample.bikeshed.stocks.shared.StockRequest;
+import com.google.gwt.sample.bikeshed.stocks.shared.StockResponse;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+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.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class StocksMobile {
+
+  interface Binder extends UiBinder<Widget, StocksMobile> {}
+  private static final Binder binder = GWT.create(Binder.class);
+
+  /**
+   * 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);
+  }
+
+  @UiField PagingTableListView<StockQuote> listView;
+  private final StockServiceAsync dataService = GWT.create(StockService.class);
+  private AsyncListModel<StockQuote> favoritesListModel;
+
+  /**
+   * The timer used to update the stock quotes.
+   */
+  private Timer updateTimer = new Timer() {
+    @Override
+    public void run() {
+      update();
+    }
+  };
+
+  /**
+   * This is the entry point method.
+   */
+  public void onModuleLoad() {
+    // Create the various models. Do this before binding the UI, because some
+    // of the UiFactories need the models to instantiate their widgets.
+    favoritesListModel = new AsyncListModel<StockQuote>() {
+      @Override
+      protected void onRangeChanged(int start, int length) {
+        update();
+      }
+    };
+
+    // Now create the UI.
+    RootPanel.get().add(binder.createAndBindUi(this));
+    update();
+  }
+
+  /**
+   * Process the {@link StockResponse} from the server.
+   * 
+   * @param response the stock response
+   */
+  public void processStockResponse(StockResponse response) {
+    // Update the favorites list.
+    updateFavorites(response);
+
+    // Restart the update timer.
+    updateTimer.schedule(UPDATE_DELAY);
+  }
+
+  public void update() {
+    Range[] favoritesRanges = favoritesListModel.getRanges();
+
+    StockRequest request = new StockRequest("TODO", null, null,
+        favoritesRanges[0], null);
+
+    dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
+      public void onFailure(Throwable caught) {
+        if (handleRpcError(caught, null)) {
+          updateTimer.schedule(UPDATE_DELAY);
+        }
+      }
+
+      public void onSuccess(StockResponse result) {
+        processStockResponse(result);
+      }
+    });
+  }
+
+  @UiFactory
+  PagingTableListView<StockQuote> createFavoritesWidget() {
+    PagingTableListView<StockQuote> listView = new PagingTableListView<StockQuote>(
+        favoritesListModel, 10);
+
+    listView.addColumn(Columns.tickerColumn, new TextHeader("ticker"));
+    listView.addColumn(Columns.priceColumn, new TextHeader("price"));
+    listView.addColumn(Columns.changeColumn, new TextHeader("change"));
+    listView.addColumn(Columns.sharesColumn, new TextHeader("shares"));
+    listView.addColumn(Columns.dollarsColumn, new TextHeader("value"));
+    listView.addColumn(Columns.profitLossColumn, new TextHeader("profit/loss"));
+
+    return listView;
+  }
+
+  /**
+   * Display a message to the user when an RPC call fails.
+   * 
+   * @param caught the exception
+   * @param displayMessage the message to display to the user, or null to
+   *          display a default message
+   * @return true if recoverable, false if not
+   */
+  private boolean handleRpcError(Throwable caught, String displayMessage) {
+    String message = caught.getMessage();
+    if (message.contains("Not logged in")) {
+      // Force the user to login.
+      Window.Location.reload();
+      return false;
+    }
+
+    if (displayMessage == null) {
+      displayMessage = "ERROR: " + caught.getMessage();
+    }
+    Window.alert(displayMessage);
+    return true;
+  }
+
+  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);
+  }
+}
+
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml
new file mode 100644
index 0000000..1c7ee0c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml
@@ -0,0 +1,14 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'
+  xmlns:t='urn:import:com.google.gwt.bikeshed.tree.client'
+  xmlns:s='urn:import:com.google.gwt.sample.bikeshed.stocks.client'>
+
+  <ui:style field='common' src='common.css'/>
+
+  <g:HTMLPanel>
+    <div styleName='{common.header-left}'>Portfolio / Favorites</div>
+    <l:PagingTableListView ui:field='listView' styleName='{common.table}'/>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
index ccaed91..74560cb 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
@@ -185,7 +185,8 @@
     Range sectorRange = request.getSectorRange();
 
     PlayerStatus player = ensurePlayer();
-    Result searchResults = getSearchQuotes(query, searchRange);
+    Result searchResults = searchRange != null ?
+        getSearchQuotes(query, searchRange) : null;
     Result favorites = queryFavorites(favoritesRange);
     String sectorName = request.getSector();
     Result sector = sectorRange != null ? getSectorQuotes(sectorName,
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
index ce4a05e..1476fe8 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
@@ -32,8 +32,9 @@
     RootPanel.get().add(tree);
 
     RootPanel.get().add(new HTML("<hr>"));
-    
-    SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(), "...", 100, 200);
+
+    SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(), "...", 100);
+    sstree.setHeight("200px");
     RootPanel.get().add(sstree);
   }
 }
diff --git a/bikeshed/war/Stocks.html b/bikeshed/war/Stocks.html
index 0b3e56c..85c4b5d 100644
--- a/bikeshed/war/Stocks.html
+++ b/bikeshed/war/Stocks.html
@@ -4,7 +4,7 @@
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <link type="text/css" rel="stylesheet" href="Stocks.css">
     <title>Stock Game Sample</title>
-    <script type="text/javascript" language="javascript" src="stocks/stocks.nocache.js"></script>
+    <script type="text/javascript" language="javascript" src="stocksdesktop/stocksdesktop.nocache.js"></script>
   </head>
 
   <body>
diff --git a/bikeshed/war/StocksMobile.html b/bikeshed/war/StocksMobile.html
new file mode 100644
index 0000000..442ed18
--- /dev/null
+++ b/bikeshed/war/StocksMobile.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <link type="text/css" rel="stylesheet" href="Stocks.css">
+    <title>Stock Game Sample</title>
+    <script type="text/javascript" language="javascript" src="stocksmobile/stocksmobile.nocache.js"></script>
+  </head>
+
+  <body>
+    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
+    <noscript>
+      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
+        Your web browser must have JavaScript enabled
+        in order for this application to display correctly.
+      </div>
+    </noscript>
+  </body>
+</html>
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml
index 096e396..ad7251e 100644
--- a/bikeshed/war/WEB-INF/web.xml
+++ b/bikeshed/war/WEB-INF/web.xml
@@ -28,7 +28,12 @@
 
   <servlet-mapping>
     <servlet-name>stockServlet</servlet-name>
-    <url-pattern>/stocks/stock</url-pattern>
+    <url-pattern>/stocksdesktop/stock</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
+    <servlet-name>stockServlet</servlet-name>
+    <url-pattern>/stocksmobile/stock</url-pattern>
   </servlet-mapping>
 
   <servlet-mapping>