--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.HistoryListener;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+/**
+ * Client side implementation for UriFragmentUtility. Uses GWT's History object
+ * as an implementation.
+ *
+ */
+public class IUriFragmentUtility extends Widget implements Paintable,
+ HistoryListener {
+
+ private String fragment;
+ private ApplicationConnection client;
+ private String paintableId;
+ private boolean immediate;
+
+ public IUriFragmentUtility() {
+ setElement(Document.get().createDivElement());
+ if (BrowserInfo.get().isIE6()) {
+ getElement().getStyle().setProperty("overflow", "hidden");
+ getElement().getStyle().setProperty("height", "0");
+ }
+ History.addHistoryListener(this);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+ String uidlFragment = uidl.getStringVariable("fragment");
+ immediate = uidl.getBooleanAttribute("immediate");
+ if (this.client == null) {
+ // initial paint has some special logic
+ this.client = client;
+ paintableId = uidl.getId();
+ if ("".equals(uidlFragment)) {
+ // empty fragment, send initial fragment to server
+ History.fireCurrentHistoryState();
+ } else {
+ // fragment initially exists server side, set that
+ History.newItem(uidlFragment, false);
+ }
+ } else {
+ if (uidlFragment != null && !uidlFragment.equals(fragment)) {
+ // normal fragment change from server, add new history item
+ History.newItem(uidlFragment, false);
+ }
+ }
+ }
+
+ public void onHistoryChanged(String historyToken) {
+ fragment = historyToken;
+ if (client != null) {
+ client.updateVariable(paintableId, "fragment", fragment, immediate);
+ }
+ }
+
+}
--- /dev/null
+package com.itmill.toolkit.tests.tickets;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.Label;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Panel;
+import com.itmill.toolkit.ui.TextField;
+import com.itmill.toolkit.ui.UriFragmentUtility;
+import com.itmill.toolkit.ui.Window;
+import com.itmill.toolkit.ui.Button.ClickEvent;
+import com.itmill.toolkit.ui.UriFragmentUtility.FragmentChangedEvent;
+
+public class Ticket34 extends Application {
+
+ private Map<String, Component> views = new HashMap<String, Component>();
+ private OrderedLayout mainLayout;
+ private Component currentView;
+ private UriFragmentUtility reader;
+
+ 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 OrderedLayout();
+ mainLayout.setSizeFull();
+ final Window mainWin = new Window(
+ "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);
+
+ setView("main");
+
+ }
+
+ private void setView(String string) {
+ Component component = views.get(string);
+ if (component == null) {
+ getMainWindow().showNotification(
+ "View called " + string + " not found!");
+ } else if (component != currentView) {
+ if (currentView != null) {
+ mainLayout.replaceComponent(currentView, component);
+ } else {
+ mainLayout.addComponent(component);
+ }
+ // give all extra space for view
+ mainLayout.setExpandRatio(component, 1);
+ currentView = component;
+ }
+ }
+
+ private void buildViews(String[] strings) {
+ for (String string : strings) {
+ Panel p = new Panel(string);
+ p.setSizeFull();
+ ((OrderedLayout) p.getLayout()).setSpacing(true);
+ p.addComponent(new Label("This is a simple test case for "
+ + "UriFragmentReader that can be used for"
+ + " adding linking, back/forward button "
+ + "and history support for ajax application. "));
+ StringBuffer sb = new StringBuffer();
+ sb.append("Available views : ");
+ for (String key : strings) {
+ sb.append(key);
+ sb.append(" ");
+ }
+ sb.append("Application will change to them from uri "
+ + "fragment or server initiated via textfield below.");
+ p.addComponent(new Label(sb.toString()));
+
+ final TextField tf = new TextField(
+ "Type view name (will change to that "
+ + "view and change the uri fragment)");
+ p.addComponent(tf);
+ Button b = new Button("Go!");
+ p.addComponent(b);
+ b.addListener(new Button.ClickListener() {
+
+ public void buttonClick(ClickEvent event) {
+ String viewName = tf.getValue().toString();
+ // fragmentChangedListener will change the view if possible
+ reader.setFragment(viewName);
+ }
+ });
+
+ views.put(string, p);
+ }
+ }
+
+}
--- /dev/null
+package com.itmill.toolkit.ui;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.itmill.toolkit.service.ApplicationContext.TransactionListener;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/**
+ * 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.
+ *
+ */
+public class UriFragmentUtility extends AbstractComponent {
+
+ /**
+ * Listener that listens changes in URI fragment.
+ */
+ public interface FragmentChangedListener {
+
+ public void fragmentChanged(FragmentChangedEvent source);
+
+ }
+
+ /**
+ * Event fired when uri fragment changes.
+ */
+ public class FragmentChangedEvent extends Component.Event {
+
+ /**
+ * Serial generated by eclipse
+ */
+ private static final long serialVersionUID = -4142140007700263197L;
+
+ /**
+ * 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 String getTag() {
+ return "urifragment";
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ target.addVariable(this, "fragment", fragment);
+ }
+
+ @Override
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+ fragment = (String) variables.get("fragment");
+ fireEvent(new FragmentChangedEvent(this));
+ }
+
+ /**
+ * Gets currently set URI fragment.
+ *
+ * Note that initial URI fragment that user used to enter the application
+ * will be read after application init. If you absolutely need that you must
+ * hook to {@link TransactionListener}
+ *
+ * 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;
+ }
+
+ /**
+ * Sets URI fragment.
+ *
+ * @param newFragment
+ */
+ public void setFragment(String newFragment) {
+ if ((newFragment == null && fragment != null)
+ || (newFragment != null && !newFragment.equals(fragment))) {
+ fragment = newFragment;
+ fireEvent(new FragmentChangedEvent(this));
+ requestRepaint();
+ }
+ }
+
+}