Add View and ViewSite to simplify off screen DOM updating

These two classes can be used to implement an off-screen DOM update,
where a widget is attached invisibly to the browser DOM, updated by
RPC calls, and then swapped with a currently visible DOM node, thus
making the new information visible instantly.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtexpui/user/client/View.java b/src/main/java/com/google/gwtexpui/user/client/View.java
new file mode 100644
index 0000000..c50625c
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/user/client/View.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// 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.gwtexpui.user.client;
+
+import com.google.gwt.user.client.ui.Composite;
+
+/**
+ * Widget to display within a {@link ViewSite}.
+ *<p>
+ * Implementations must override <code>protected void onLoad()</code> and
+ * arrange for {@link #display()} to be invoked once the DOM within the view is
+ * consistent for presentation to the user. Typically this means that the
+ * subclass can start RPCs within <code>onLoad()</code> and then invoke
+ * <code>display()</code> from within the AsyncCallback's
+ * <code>onSuccess(Object)</code> method.
+ */
+public abstract class View extends Composite {
+  ViewSite<? extends View> site;
+
+  @Override
+  protected void onUnload() {
+    site = null;
+    super.onUnload();
+  }
+
+  /** Replace the current view in the parent ViewSite with this view. */
+  public final void display() {
+    if (site != null) {
+      site.swap(this);
+    }
+  }
+}
diff --git a/src/main/java/com/google/gwtexpui/user/client/ViewSite.java b/src/main/java/com/google/gwtexpui/user/client/ViewSite.java
new file mode 100644
index 0000000..d8c6b60
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/user/client/ViewSite.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// 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.gwtexpui.user.client;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+/**
+ * Hosts a single {@link View}.
+ * <p>
+ * View instances are attached inside of an invisible DOM node, permitting their
+ * <code>onLoad()</code> method to be invoked and to update the DOM prior to the
+ * elements being made visible in the UI.
+ * <p>
+ * Complaint View instances must invoke {@link View#display()} once the DOM is
+ * ready for presentation.
+ */
+public class ViewSite<V extends View> extends Composite {
+  private final FlowPanel main;
+  private SimplePanel current;
+  private SimplePanel next;
+
+  public ViewSite() {
+    main = new FlowPanel();
+    initWidget(main);
+  }
+
+  /** Get the current view; null if there is no view being displayed. */
+  @SuppressWarnings("unchecked")
+  public V getView() {
+    return current != null ? (V) current.getWidget() : null;
+  }
+
+  /**
+   * Set the next view to display.
+   * <p>
+   * The view will be attached to the DOM tree within a hidden container,
+   * permitting its <code>onLoad()</code> method to execute and update the DOM
+   * without the user seeing the result.
+   * 
+   * @param view the next view to display.
+   */
+  public void setView(final V view) {
+    if (next != null) {
+      main.remove(next);
+    }
+    view.site = this;
+    next = new SimplePanel();
+    next.setVisible(false);
+    main.add(next);
+    next.add(view);
+  }
+
+  final void swap(final View v) {
+    if (next != null && next.getWidget() == v) {
+      if (current != null) {
+        main.remove(current);
+      }
+      current = next;
+      next = null;
+      current.setVisible(true);
+    }
+  }
+}