]> source.dussan.org Git - vaadin-framework.git/commitdiff
URI fragment support in Root (#8048)
authorLeif Åstrand <leif@vaadin.com>
Wed, 7 Dec 2011 14:34:59 +0000 (16:34 +0200)
committerLeif Åstrand <leif@vaadin.com>
Wed, 7 Dec 2011 14:34:59 +0000 (16:34 +0200)
src/com/vaadin/Application.java
src/com/vaadin/terminal/CombinedRequest.java
src/com/vaadin/terminal/gwt/client/ui/VView.java
src/com/vaadin/ui/Root.java
src/com/vaadin/ui/UriFragmentUtility.java [deleted file]
tests/server-side/com/vaadin/tests/server/component/urifragmentutility/UriFragmentUtilityListeners.java
tests/testbench/com/vaadin/tests/TestBench.java
tests/testbench/com/vaadin/tests/components/root/UriFragmentTest.java [new file with mode: 0644]
tests/testbench/com/vaadin/tests/tickets/Ticket34.java

index e74143d0c862034e86b9c19c46ea0b59a90184c2..97afadc653e2b96e2620d8d9233e6d315e8f3038 100644 (file)
@@ -2250,12 +2250,12 @@ public class Application implements Terminal.ErrorListener, Serializable {
                         pendingRoots.put(Integer.valueOf(id),
                                 new PendingRootRequest(request));
                     } else {
-                        root.init(request);
+                        root.doInit(request);
                     }
                 }
             } else if (pendingRootRequest != null) {
                 // We have a root, but the init has been pending
-                root.init(request);
+                root.doInit(request);
             }
         }
 
index c589baad63dd975d98774341ab3e58482ef9a60e..a205f0e01e419028807c8c9129c677e575ed01df 100644 (file)
@@ -87,7 +87,13 @@ public class CombinedRequest implements WrappedRequest {
     public BrowserDetails getBrowserDetails() {
         return new BrowserDetails() {
             public String getUriFragmet() {
-                return secondRequest.getParameter("f");
+                String fragment = secondRequest.getParameter("f");
+                if (fragment == null || fragment.length() == 0) {
+                    return "";
+                } else {
+                    // Trim the initial # char
+                    return fragment.substring(1);
+                }
             }
 
             public String getWindowName() {
index 02da3dc18599da2811aa7f42bb79fe738fb49ae6..f2b5dd2e2ea75eedd51588d1a379bdd4e738d332 100644 (file)
@@ -20,11 +20,14 @@ import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.DomEvent.Type;
 import com.google.gwt.event.logical.shared.ResizeEvent;
 import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.History;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.RootPanel;
@@ -47,6 +50,8 @@ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHan
 public class VView extends SimplePanel implements Container, ResizeHandler,
         Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable {
 
+    public static final String FRAGMENT_VARIABLE = "fragment";
+
     private static final String CLASSNAME = "v-view";
 
     public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain";
@@ -96,6 +101,31 @@ public class VView extends SimplePanel implements Container, ResizeHandler,
      */
     private Element parentFrame;
 
+    private HandlerRegistration historyHandlerRegistration;
+
+    /**
+     * The current URI fragment, used to avoid sending updates if nothing has
+     * changed.
+     */
+    private String currentFragment;
+
+    /**
+     * Listener for URI fragment changes. Notifies the server of the new value
+     * whenever the value changes.
+     */
+    private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
+        public void onValueChange(ValueChangeEvent<String> event) {
+            String newFragment = event.getValue();
+
+            // Send the new fragment to the server if it has changed
+            if (!newFragment.equals(currentFragment) && connection != null) {
+                currentFragment = newFragment;
+                connection.updateVariable(id, FRAGMENT_VARIABLE, newFragment,
+                        true);
+            }
+        }
+    };
+
     private ClickEventHandler clickEventHandler = new ClickEventHandler(this,
             VPanel.CLICK_EVENT_IDENTIFIER) {
 
@@ -124,6 +154,21 @@ public class VView extends SimplePanel implements Container, ResizeHandler,
         getElement().setTabIndex(-1);
     }
 
+    @Override
+    protected void onAttach() {
+        super.onAttach();
+        historyHandlerRegistration = History
+                .addValueChangeHandler(historyChangeHandler);
+        currentFragment = History.getToken();
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        historyHandlerRegistration.removeHandler();
+        historyHandlerRegistration = null;
+    }
+
     /**
      * Called when the window might have been resized.
      * 
@@ -396,6 +441,20 @@ public class VView extends SimplePanel implements Container, ResizeHandler,
 
         scrollIntoView(uidl);
 
+        if (uidl.hasAttribute(FRAGMENT_VARIABLE)) {
+            currentFragment = uidl.getStringAttribute(FRAGMENT_VARIABLE);
+            if (!currentFragment.equals(History.getToken())) {
+                History.newItem(currentFragment, true);
+            }
+        } else {
+            // Initial request for which the server doesn't yet have a fragment
+            // (and haven't shown any interest in getting one)
+            currentFragment = History.getToken();
+
+            // Include current fragment in the next request
+            client.updateVariable(id, FRAGMENT_VARIABLE, currentFragment, false);
+        }
+
         rendering = false;
     }
 
index bcc9c443c388091fec51bcf47f3c8ef3d0b129b0..03bb1023ac6db00d7c0c9aa65730bf74b6337d91 100644 (file)
@@ -5,6 +5,7 @@
 package com.vaadin.ui;
 
 import java.io.Serializable;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -72,6 +73,67 @@ import com.vaadin.ui.Window.ResizeListener;
 public class Root extends AbstractComponentContainer implements
         Action.Container, Action.Notifier {
 
+    /**
+     * Listener that listens changes in URI fragment.
+     */
+    public interface FragmentChangedListener extends Serializable {
+        public void fragmentChanged(FragmentChangedEvent event);
+    }
+
+    /**
+     * Event fired when uri fragment changes.
+     */
+    public class FragmentChangedEvent extends Component.Event {
+
+        /**
+         * The new uri fragment
+         */
+        private final String fragment;
+
+        /**
+         * Creates a new instance of UriFragmentReader change event.
+         * 
+         * @param source
+         *            the Source of the event.
+         */
+        public FragmentChangedEvent(Root source, String fragment) {
+            super(source);
+            this.fragment = fragment;
+        }
+
+        /**
+         * Gets the root in which the fragment has changed.
+         * 
+         * @return the root in which the fragment has changed
+         */
+        public Root getRoot() {
+            return (Root) getComponent();
+        }
+
+        /**
+         * Get the new fragment
+         * 
+         * @return the new fragment
+         */
+        public String getFragment() {
+            return fragment;
+        }
+    }
+
+    private static final Method FRAGMENT_CHANGED_METHOD;
+
+    static {
+        try {
+            FRAGMENT_CHANGED_METHOD = FragmentChangedListener.class
+                    .getDeclaredMethod("fragmentChanged",
+                            new Class[] { FragmentChangedEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in FragmentChangedListener");
+        }
+    }
+
     /**
      * A border style used for opening resources in a window without a border.
      */
@@ -303,6 +365,10 @@ public class Root extends AbstractComponentContainer implements
         if (actionManager != null) {
             actionManager.paintActions(null, target);
         }
+
+        if (fragment != null) {
+            target.addAttribute(VView.FRAGMENT_VARIABLE, fragment);
+        }
     }
 
     @Override
@@ -313,6 +379,11 @@ public class Root extends AbstractComponentContainer implements
         if (actionManager != null) {
             actionManager.handleActions(variables, this);
         }
+
+        if (variables.containsKey(VView.FRAGMENT_VARIABLE)) {
+            String fragment = (String) variables.get(VView.FRAGMENT_VARIABLE);
+            setFragment(fragment, true);
+        }
     }
 
     public Iterator<Component> getComponentIterator() {
@@ -465,6 +536,11 @@ public class Root extends AbstractComponentContainer implements
      */
     private Focusable pendingFocus;
 
+    /**
+     * The current URI fragment.
+     */
+    private String fragment;
+
     /**
      * This method is used by Component.Focusable objects to request focus to
      * themselves. Focus renders must be handled at window level (instead of
@@ -791,6 +867,24 @@ public class Root extends AbstractComponentContainer implements
         }
     }
 
+    /**
+     * Internal initialization method, should not be overridden. This method is
+     * not declared as final because that would break compatibility with e.g.
+     * CDI.
+     * 
+     * @param request
+     *            the initialization request
+     */
+    public void doInit(WrappedRequest request) {
+        BrowserDetails browserDetails = request.getBrowserDetails();
+        if (browserDetails != null) {
+            fragment = browserDetails.getUriFragmet();
+        }
+
+        // Call the init overridden by the application developer
+        init(request);
+    }
+
     /**
      * Initializes this root. This method is intended to be overridden by
      * subclasses to build the view and configure non-component functionality.
@@ -807,7 +901,7 @@ public class Root extends AbstractComponentContainer implements
      * @param request
      *            the wrapped request that caused this root to be created
      */
-    public void init(WrappedRequest request) {
+    protected void init(WrappedRequest request) {
         // Default implementation doesn't do anything
     }
 
@@ -1100,6 +1194,62 @@ public class Root extends AbstractComponentContainer implements
                 listener);
     }
 
+    public void addListener(FragmentChangedListener listener) {
+        addListener(FragmentChangedEvent.class, listener,
+                FRAGMENT_CHANGED_METHOD);
+    }
+
+    public void removeListener(FragmentChangedListener listener) {
+        removeListener(FragmentChangedEvent.class, listener,
+                FRAGMENT_CHANGED_METHOD);
+    }
+
+    /**
+     * Sets URI fragment. Optionally fires a {@link FragmentChangedEvent}
+     * 
+     * @param newFragment
+     *            id of the new fragment
+     * @param fireEvent
+     *            true to fire event
+     * @see FragmentChangedEvent
+     * @see FragmentChangedListener
+     */
+    public void setFragment(String newFragment, boolean fireEvents) {
+        if (newFragment == null) {
+            throw new NullPointerException("The fragment may not be null");
+        }
+        if (!newFragment.equals(fragment)) {
+            fragment = newFragment;
+            if (fireEvents) {
+                fireEvent(new FragmentChangedEvent(this, newFragment));
+            }
+            requestRepaint();
+        }
+    }
+
+    /**
+     * Sets URI fragment. This method fires a {@link FragmentChangedEvent}
+     * 
+     * @param newFragment
+     *            id of the new fragment
+     * @see FragmentChangedEvent
+     * @see FragmentChangedListener
+     */
+    public void setFragment(String newFragment) {
+        setFragment(newFragment, true);
+    }
+
+    /**
+     * Gets currently set URI fragment.
+     * <p>
+     * To listen changes in fragment, hook a {@link FragmentChangedListener}.
+     * 
+     * @return the current fragment in browser uri or null if not known
+     */
+    public String getFragment() {
+        return fragment;
+    }
+
     public void addListener(ResizeListener resizeListener) {
         throw new RuntimeException("Not yet implemented");
     }
diff --git a/src/com/vaadin/ui/UriFragmentUtility.java b/src/com/vaadin/ui/UriFragmentUtility.java
deleted file mode 100644 (file)
index de21aff..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-@ITMillApache2LicenseForJavaFiles@
- */
-package com.vaadin.ui;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.Map;
-
-import com.vaadin.terminal.PaintException;
-import com.vaadin.terminal.PaintTarget;
-import com.vaadin.terminal.gwt.client.ui.VUriFragmentUtility;
-import com.vaadin.ui.ClientWidget.LoadStyle;
-
-/**
- * Experimental web browser dependent component for URI fragment (part after
- * hash mark "#") reading and writing.
- * 
- * Component can be used to workaround common ajax web applications pitfalls:
- * bookmarking a program state and back button.
- * 
- */
-@SuppressWarnings("serial")
-@ClientWidget(value = VUriFragmentUtility.class, loadStyle = LoadStyle.EAGER)
-public class UriFragmentUtility extends AbstractComponent {
-
-    /**
-     * Listener that listens changes in URI fragment.
-     */
-    public interface FragmentChangedListener extends Serializable {
-
-        public void fragmentChanged(FragmentChangedEvent source);
-
-    }
-
-    /**
-     * Event fired when uri fragment changes.
-     */
-    public class FragmentChangedEvent extends Component.Event {
-
-        /**
-         * Creates a new instance of UriFragmentReader change event.
-         * 
-         * @param source
-         *            the Source of the event.
-         */
-        public FragmentChangedEvent(Component source) {
-            super(source);
-        }
-
-        /**
-         * Gets the UriFragmentReader where the event occurred.
-         * 
-         * @return the Source of the event.
-         */
-        public UriFragmentUtility getUriFragmentUtility() {
-            return (UriFragmentUtility) getSource();
-        }
-    }
-
-    private static final Method FRAGMENT_CHANGED_METHOD;
-
-    static {
-        try {
-            FRAGMENT_CHANGED_METHOD = FragmentChangedListener.class
-                    .getDeclaredMethod("fragmentChanged",
-                            new Class[] { FragmentChangedEvent.class });
-        } catch (final java.lang.NoSuchMethodException e) {
-            // This should never happen
-            throw new java.lang.RuntimeException(
-                    "Internal error finding methods in FragmentChangedListener");
-        }
-    }
-
-    public void addListener(FragmentChangedListener listener) {
-        addListener(FragmentChangedEvent.class, listener,
-                FRAGMENT_CHANGED_METHOD);
-    }
-
-    public void removeListener(FragmentChangedListener listener) {
-        removeListener(FragmentChangedEvent.class, listener,
-                FRAGMENT_CHANGED_METHOD);
-    }
-
-    private String fragment;
-
-    public UriFragmentUtility() {
-        // immediate by default
-        setImmediate(true);
-    }
-
-    @Override
-    public void paintContent(PaintTarget target) throws PaintException {
-        super.paintContent(target);
-        target.addVariable(this, "fragment", fragment);
-    }
-
-    @Override
-    public void changeVariables(Object source, Map<String, Object> variables) {
-        super.changeVariables(source, variables);
-        fragment = (String) variables.get("fragment");
-        fireEvent(new FragmentChangedEvent(this));
-    }
-
-    /**
-     * Gets currently set URI fragment.
-     * <p>
-     * To listen changes in fragment, hook a {@link FragmentChangedListener}.
-     * <p>
-     * Note that initial URI fragment that user used to enter the application
-     * will be read after application init. It fires FragmentChangedEvent only
-     * if it is not the same as on server side.
-     * 
-     * @return the current fragment in browser uri or null if not known
-     */
-    public String getFragment() {
-        return fragment;
-    }
-
-    /**
-     * Sets URI fragment. Optionally fires a {@link FragmentChangedEvent}
-     * 
-     * @param newFragment
-     *            id of the new fragment
-     * @param fireEvent
-     *            true to fire event
-     * @see FragmentChangedEvent
-     * @see FragmentChangedListener
-     */
-    public void setFragment(String newFragment, boolean fireEvent) {
-        if ((newFragment == null && fragment != null)
-                || (newFragment != null && !newFragment.equals(fragment))) {
-            fragment = newFragment;
-            if (fireEvent) {
-                fireEvent(new FragmentChangedEvent(this));
-            }
-            requestRepaint();
-        }
-    }
-
-    /**
-     * Sets URI fragment. This method fires a {@link FragmentChangedEvent}
-     * 
-     * @param newFragment
-     *            id of the new fragment
-     * @see FragmentChangedEvent
-     * @see FragmentChangedListener
-     */
-    public void setFragment(String newFragment) {
-        setFragment(newFragment, true);
-    }
-
-}
index fe400e55b9dd9714dfec9527f4a2fd4397315b93..08bc019a4dc7667e0bb94b083fd8e21338e3671a 100644 (file)
@@ -1,13 +1,13 @@
 package com.vaadin.tests.server.component.urifragmentutility;\r
 \r
 import com.vaadin.tests.server.component.AbstractListenerMethodsTest;\r
-import com.vaadin.ui.UriFragmentUtility;\r
-import com.vaadin.ui.UriFragmentUtility.FragmentChangedEvent;\r
-import com.vaadin.ui.UriFragmentUtility.FragmentChangedListener;\r
+import com.vaadin.ui.Root;\r
+import com.vaadin.ui.Root.FragmentChangedEvent;\r
+import com.vaadin.ui.Root.FragmentChangedListener;\r
 \r
 public class UriFragmentUtilityListeners extends AbstractListenerMethodsTest {\r
     public void testFragmentChangedListenerAddGetRemove() throws Exception {\r
-        testListenerAddGetRemove(UriFragmentUtility.class,\r
-                FragmentChangedEvent.class, FragmentChangedListener.class);\r
+        testListenerAddGetRemove(Root.class, FragmentChangedEvent.class,\r
+                FragmentChangedListener.class);\r
     }\r
 }\r
index 37ed3b79e718e7ac8c683fc3f0c16123d1379802..115ff47a57e61b0390216eaad22c135a46ef7b0d 100644 (file)
@@ -24,9 +24,8 @@ import com.vaadin.ui.Layout;
 import com.vaadin.ui.Link;
 import com.vaadin.ui.Panel;
 import com.vaadin.ui.Root;
+import com.vaadin.ui.Root.FragmentChangedEvent;
 import com.vaadin.ui.Tree;
-import com.vaadin.ui.UriFragmentUtility;
-import com.vaadin.ui.UriFragmentUtility.FragmentChangedEvent;
 import com.vaadin.ui.VerticalLayout;
 
 /**
@@ -119,12 +118,9 @@ public class TestBench extends com.vaadin.Application.LegacyApplication
         VerticalLayout lo = new VerticalLayout();
         lo.addComponent(menu);
 
-        UriFragmentUtility uri = new UriFragmentUtility();
-        lo.addComponent(uri);
-
-        uri.addListener(new UriFragmentUtility.FragmentChangedListener() {
+        mainWindow.addListener(new Root.FragmentChangedListener() {
             public void fragmentChanged(FragmentChangedEvent source) {
-                String fragment = source.getUriFragmentUtility().getFragment();
+                String fragment = source.getFragment();
                 if (fragment != null && !"".equals(fragment)) {
                     // try to find a proper test class
 
diff --git a/tests/testbench/com/vaadin/tests/components/root/UriFragmentTest.java b/tests/testbench/com/vaadin/tests/components/root/UriFragmentTest.java
new file mode 100644 (file)
index 0000000..9cab551
--- /dev/null
@@ -0,0 +1,53 @@
+package com.vaadin.tests.components.root;
+
+import com.vaadin.annotations.RootInitRequiresBrowserDetals;
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.tests.components.AbstractTestRoot;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Label;
+
+@RootInitRequiresBrowserDetals
+public class UriFragmentTest extends AbstractTestRoot {
+
+    private final Label fragmentLabel = new Label();
+
+    @Override
+    protected void setup(WrappedRequest request) {
+        addComponent(fragmentLabel);
+        updateLabel();
+        addListener(new FragmentChangedListener() {
+            public void fragmentChanged(FragmentChangedEvent event) {
+                updateLabel();
+            }
+        });
+        addComponent(new Button("Navigate to #test",
+                new Button.ClickListener() {
+                    public void buttonClick(ClickEvent event) {
+                        setFragment("test");
+                    }
+                }));
+    }
+
+    private void updateLabel() {
+        String fragment = getFragment();
+        if (fragment == null) {
+            fragmentLabel.setValue("No URI fragment set");
+        } else {
+            fragmentLabel.setValue("Current URI fragment: " + fragment);
+        }
+    }
+
+    @Override
+    public String getTestDescription() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
index 5181ac7d87d1668545ed07e8f0c8f86367bfa258..1fba8bfc56a52040c7d104d88af0e45c63255410 100644 (file)
@@ -10,9 +10,8 @@ import com.vaadin.ui.Component;
 import com.vaadin.ui.Label;
 import com.vaadin.ui.Panel;
 import com.vaadin.ui.Root;
+import com.vaadin.ui.Root.FragmentChangedEvent;
 import com.vaadin.ui.TextField;
-import com.vaadin.ui.UriFragmentUtility;
-import com.vaadin.ui.UriFragmentUtility.FragmentChangedEvent;
 import com.vaadin.ui.VerticalLayout;
 
 public class Ticket34 extends Application.LegacyApplication {
@@ -20,34 +19,27 @@ public class Ticket34 extends Application.LegacyApplication {
     private Map<String, Component> views = new HashMap<String, Component>();
     private VerticalLayout mainLayout;
     private Component currentView;
-    private UriFragmentUtility reader;
 
     @Override
     public void init() {
 
         buildViews(new String[] { "main", "view2", "view3" });
 
-        reader = new UriFragmentUtility();
-        reader.addListener(new UriFragmentUtility.FragmentChangedListener() {
-
-            public void fragmentChanged(FragmentChangedEvent event) {
-                getMainWindow().showNotification(
-                        "Fragment now: "
-                                + event.getUriFragmentUtility().getFragment());
-                // try to change to view mapped by fragment string
-                setView(event.getUriFragmentUtility().getFragment());
-            }
-        });
-
         mainLayout = new VerticalLayout();
         mainLayout.setSizeFull();
         final Root mainWin = new Root(
                 "Test app for URI fragment management/reading", mainLayout);
         setMainWindow(mainWin);
 
-        // UriFragmentReader is 0px size by default, so it will not render
-        // anything on screen
-        mainLayout.addComponent(reader);
+        mainWin.addListener(new Root.FragmentChangedListener() {
+
+            public void fragmentChanged(FragmentChangedEvent event) {
+                getMainWindow().showNotification(
+                        "Fragment now: " + event.getFragment());
+                // try to change to view mapped by fragment string
+                setView(event.getFragment());
+            }
+        });
 
         setView("main");
 
@@ -100,7 +92,7 @@ public class Ticket34 extends Application.LegacyApplication {
                 public void buttonClick(ClickEvent event) {
                     String viewName = tf.getValue().toString();
                     // fragmentChangedListener will change the view if possible
-                    reader.setFragment(viewName);
+                    event.getButton().getRoot().setFragment(viewName);
                 }
             });