aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/vaadin/ui/Root.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/vaadin/ui/Root.java')
-rw-r--r--src/com/vaadin/ui/Root.java434
1 files changed, 410 insertions, 24 deletions
diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java
index 30659d98e3..0358cba4e8 100644
--- a/src/com/vaadin/ui/Root.java
+++ b/src/com/vaadin/ui/Root.java
@@ -1,55 +1,441 @@
package com.vaadin.ui;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
import com.vaadin.Application;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.Terminal;
+import com.vaadin.terminal.gwt.client.ui.VView;
+import com.vaadin.ui.Window.CloseListener;
import com.vaadin.ui.Window.Notification;
-public interface Root extends Component, com.vaadin.ui.Component.Focusable {
+@ClientWidget(VView.class)
+public class Root extends AbstractComponentContainer {
+ private final Component content;
+ private Terminal terminal;
+ private Application application;
/**
- * Sets the application this root is attached to.
+ * A list of notifications that are waiting to be sent to the client.
+ * Cleared (set to null) when the notifications have been sent.
+ */
+ private List<Notification> notifications;
+
+ /**
+ * A list of javascript commands that are waiting to be sent to the client.
+ * Cleared (set to null) when the commands have been sent.
+ */
+ private List<String> jsExecQueue = null;
+
+ /**
+ * List of windows in this root.
+ */
+ private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>();
+
+ /**
+ * The component that should be scrolled into view after the next repaint.
+ * Null if nothing should be scrolled into view.
+ */
+ private Component scrollIntoView;
+
+ public Root(Component content) {
+ this.content = content;
+ addComponent(content);
+ }
+
+ @Override
+ public Root getRoot() {
+ return this;
+ }
+
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Application getApplication() {
+ return application;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ content.paint(target);
+
+ // Paint subwindows
+ for (final Iterator<Window> i = windows.iterator(); i.hasNext();) {
+ final Window w = i.next();
+ w.paint(target);
+ }
+
+ // Paint notifications
+ if (notifications != null) {
+ target.startTag("notifications");
+ for (final Iterator<Notification> it = notifications.iterator(); it
+ .hasNext();) {
+ final Notification n = it.next();
+ target.startTag("notification");
+ if (n.getCaption() != null) {
+ target.addAttribute("caption", n.getCaption());
+ }
+ if (n.getDescription() != null) {
+ target.addAttribute("message", n.getDescription());
+ }
+ if (n.getIcon() != null) {
+ target.addAttribute("icon", n.getIcon());
+ }
+ if (!n.isHtmlContentAllowed()) {
+ target.addAttribute(
+ VView.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED, true);
+ }
+ target.addAttribute("position", n.getPosition());
+ target.addAttribute("delay", n.getDelayMsec());
+ if (n.getStyleName() != null) {
+ target.addAttribute("style", n.getStyleName());
+ }
+ target.endTag("notification");
+ }
+ target.endTag("notifications");
+ notifications = null;
+ }
+
+ // Add executable javascripts if needed
+ if (jsExecQueue != null) {
+ for (String script : jsExecQueue) {
+ target.startTag("execJS");
+ target.addAttribute("script", script);
+ target.endTag("execJS");
+ }
+ jsExecQueue = null;
+ }
+
+ if (scrollIntoView != null) {
+ target.addAttribute("scrollTo", scrollIntoView);
+ scrollIntoView = null;
+ }
+
+ if (pendingFocus != null) {
+ // ensure focused component is still attached to this main window
+ if (pendingFocus.getRoot() == this
+ || (pendingFocus.getRoot() != null && pendingFocus
+ .getRoot().getParent() == this)) {
+ target.addAttribute("focused", pendingFocus);
+ }
+ pendingFocus = null;
+ }
+ }
+
+ public Iterator<Component> getComponentIterator() {
+ return Collections.singleton(content).iterator();
+ }
+
+ public String getName() {
+ return "";
+ }
+
+ public Terminal getTerminal() {
+ return terminal;
+ }
+
+ public void setTerminal(Terminal terminal) {
+ this.terminal = terminal;
+ }
+
+ public void setApplication(Application application) {
+ if (application == null) {
+ throw new NullPointerException("application");
+ } else if (this.application != null) {
+ throw new IllegalStateException("Application has already been set");
+ } else {
+ this.application = application;
+ }
+ }
+
+ /**
+ * Adds a window inside this root.
*
* <p>
- * This method is called by the framework and should not be called directly
- * from application code.
+ * Adding windows inside another window creates "subwindows". These windows
+ * should not be added to application directly and are not accessible
+ * directly with any url. Addding windows implicitly sets their parents.
* </p>
*
- * @param application
- * the application the root is attached to
+ * <p>
+ * Only one level of subwindows are supported. Thus you can add windows
+ * inside such windows whose parent is <code>null</code>.
+ * </p>
+ *
+ * @param window
+ * @throws IllegalArgumentException
+ * if a window is added inside non-application level window.
+ * @throws NullPointerException
+ * if the given <code>Window</code> is <code>null</code>.
*/
- public void setApplication(Application application);
+ public void addWindow(Window window) throws IllegalArgumentException,
+ NullPointerException {
- // TODO is this required?
- public String getName();
+ if (window == null) {
+ throw new NullPointerException("Argument must not be null");
+ }
- public Terminal getTerminal();
+ if (window.getApplication() != null) {
+ throw new IllegalArgumentException(
+ "Window is already attached to an application.");
+ }
- public void setTerminal(Terminal terminal);
+ attachWindow(window);
+ }
- public void addWindow(Window window);
+ private void attachWindow(Window w) {
+ windows.add(w);
+ w.setParent(this);
+ requestRepaint();
+ }
- public boolean removeWindow(Window window);
+ /**
+ * Remove the given subwindow from this root.
+ *
+ * Since Vaadin 6.5, {@link CloseListener}s are called also when explicitly
+ * removing a window by calling this method.
+ *
+ * Since Vaadin 6.5, returns a boolean indicating if the window was removed
+ * or not.
+ *
+ * @param window
+ * Window to be removed.
+ * @return true if the subwindow was removed, false otherwise
+ */
+ public boolean removeWindow(Window window) {
+ if (!windows.remove(window)) {
+ // Window window is not a subwindow of this root.
+ return false;
+ }
+ window.setParent(null);
+ window.fireClose();
+ requestRepaint();
- public Collection<Window> getWindows();
+ return true;
+ }
- public void setFocusedComponent(Focusable focusable);
+ public Collection<Window> getWindows() {
+ return Collections.unmodifiableCollection(windows);
+ }
- public void showNotification(Notification notification);
+ @Override
+ public void focus() {
+ super.focus();
+ }
- public void showNotification(String caption, String description,
- int type, boolean htmlContentAllowed);
+ /**
+ * Component that should be focused after the next repaint. Null if no focus
+ * change should take place.
+ */
+ private Focusable pendingFocus;
- public void showNotification(String caption, String description,
- int type);
+ /**
+ * This method is used by Component.Focusable objects to request focus to
+ * themselves. Focus renders must be handled at window level (instead of
+ * Component.Focusable) due we want the last focused component to be focused
+ * in client too. Not the one that is rendered last (the case we'd get if
+ * implemented in Focusable only).
+ *
+ * To focus component from Vaadin application, use Focusable.focus(). See
+ * {@link Focusable}.
+ *
+ * @param focusable
+ * to be focused on next paint
+ */
+ public void setFocusedComponent(Focusable focusable) {
+ pendingFocus = focusable;
+ requestRepaint();
+ }
- public void showNotification(String caption, String description);
+ /**
+ * Shows a notification message on the middle of the window. The message
+ * automatically disappears ("humanized message").
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption is
+ * rendered as html.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ */
+ public void showNotification(String caption) {
+ addNotification(new Notification(caption));
+ }
+
+ /**
+ * Shows a notification message the window. The position and behavior of the
+ * message depends on the type, which is one of the basic types defined in
+ * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption is
+ * rendered as html.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ * @param type
+ * The message type
+ */
+ public void showNotification(String caption, int type) {
+ addNotification(new Notification(caption, type));
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description on the middle of the window. The message automatically
+ * disappears ("humanized message").
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption and
+ * description are rendered as html.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ *
+ */
+ public void showNotification(String caption, String description) {
+ addNotification(new Notification(caption, description));
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description. The position and behavior of the message depends on the
+ * type, which is one of the basic types defined in {@link Notification},
+ * for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption and
+ * description are rendered as html.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ * @param type
+ * The message type
+ */
+ public void showNotification(String caption, String description, int type) {
+ addNotification(new Notification(caption, description, type));
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description. The position and behavior of the message depends on the
+ * type, which is one of the basic types defined in {@link Notification},
+ * for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to avoid XSS vulnerabilities if html content is
+ * allowed.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message caption
+ * @param description
+ * The message description
+ * @param type
+ * The type of message
+ * @param htmlContentAllowed
+ * Whether html in the caption and description should be
+ * displayed as html or as plain text
+ */
+ public void showNotification(String caption, String description, int type,
+ boolean htmlContentAllowed) {
+ addNotification(new Notification(caption, description, type,
+ htmlContentAllowed));
+ }
- public void showNotification(String caption, int type);
+ /**
+ * Shows a notification message.
+ *
+ * @see Notification
+ * @see #showNotification(String)
+ * @see #showNotification(String, int)
+ * @see #showNotification(String, String)
+ * @see #showNotification(String, String, int)
+ *
+ * @param notification
+ * The notification message to show
+ */
+ public void showNotification(Notification notification) {
+ addNotification(notification);
+ }
+
+ private void addNotification(Notification notification) {
+ if (notifications == null) {
+ notifications = new LinkedList<Notification>();
+ }
+ notifications.add(notification);
+ requestRepaint();
+ }
+
+ /**
+ * Executes JavaScript in this root.
+ *
+ * <p>
+ * This method allows one to inject javascript from the server to client. A
+ * client implementation is not required to implement this functionality,
+ * but currently all web-based clients do implement this.
+ * </p>
+ *
+ * <p>
+ * Executing javascript this way often leads to cross-browser compatibility
+ * issues and regressions that are hard to resolve. Use of this method
+ * should be avoided and instead it is recommended to create new widgets
+ * with GWT. For more info on creating own, reusable client-side widgets in
+ * Java, read the corresponding chapter in Book of Vaadin.
+ * </p>
+ *
+ * @param script
+ * JavaScript snippet that will be executed.
+ */
+ public void executeJavaScript(String script) {
+ if (jsExecQueue == null) {
+ jsExecQueue = new ArrayList<String>();
+ }
- public void showNotification(String caption);
+ jsExecQueue.add(script);
- public void executeJavaScript(String script);
+ requestRepaint();
+ }
+ /**
+ * Scrolls any component between the component and root to a suitable
+ * position so the component is visible to the user. The given component
+ * must belong to this root.
+ *
+ * @param component
+ * the component to be scrolled into view
+ * @throws IllegalArgumentException
+ * if {@code component} does not belong to this root
+ */
+ public void scrollIntoView(Component component)
+ throws IllegalArgumentException {
+ if (component.getRoot() != this) {
+ throw new IllegalArgumentException(
+ "The component where to scroll must belong to this root.");
+ }
+ scrollIntoView = component;
+ requestRepaint();
+ }
}