summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rwxr-xr-xclient/src/com/vaadin/DefaultWidgetSet.gwt.xml4
-rw-r--r--client/src/com/vaadin/Vaadin.gwt.xml1
-rw-r--r--client/src/com/vaadin/client/ApplicationConfiguration.java27
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java139
-rw-r--r--client/src/com/vaadin/client/ComponentLocator.java700
-rw-r--r--client/src/com/vaadin/client/ConnectorMap.java2
-rw-r--r--client/src/com/vaadin/client/Profiler.java4
-rw-r--r--client/src/com/vaadin/client/SimpleTree.java8
-rw-r--r--client/src/com/vaadin/client/Util.java3
-rw-r--r--client/src/com/vaadin/client/communication/AtmospherePushConnection.java12
-rw-r--r--client/src/com/vaadin/client/communication/Date_Serializer.java44
-rw-r--r--client/src/com/vaadin/client/communication/Heartbeat.java171
-rw-r--r--client/src/com/vaadin/client/communication/JSONSerializer.java7
-rw-r--r--client/src/com/vaadin/client/componentlocator/ComponentLocator.java220
-rw-r--r--client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java719
-rw-r--r--client/src/com/vaadin/client/componentlocator/LocatorStrategy.java122
-rw-r--r--client/src/com/vaadin/client/componentlocator/LocatorUtil.java76
-rw-r--r--client/src/com/vaadin/client/componentlocator/SelectorPredicate.java228
-rw-r--r--client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java748
-rw-r--r--client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java267
-rw-r--r--client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java107
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchyPanel.java178
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchySection.java474
-rw-r--r--client/src/com/vaadin/client/debug/internal/Highlight.java58
-rw-r--r--client/src/com/vaadin/client/debug/internal/Icon.java2
-rw-r--r--client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java137
-rw-r--r--client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java37
-rw-r--r--client/src/com/vaadin/client/debug/internal/SelectorPath.java238
-rw-r--r--client/src/com/vaadin/client/debug/internal/TestBenchSection.java281
-rw-r--r--client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java8
-rw-r--r--client/src/com/vaadin/client/metadata/Property.java8
-rw-r--r--client/src/com/vaadin/client/metadata/TypeDataStore.java183
-rw-r--r--client/src/com/vaadin/client/ui/SubPartAware.java4
-rw-r--r--client/src/com/vaadin/client/ui/UnknownComponentConnector.java6
-rw-r--r--client/src/com/vaadin/client/ui/VAccordion.java1
-rw-r--r--client/src/com/vaadin/client/ui/VCalendarPanel.java11
-rw-r--r--client/src/com/vaadin/client/ui/VFilterSelect.java4
-rw-r--r--client/src/com/vaadin/client/ui/VLabel.java8
-rw-r--r--client/src/com/vaadin/client/ui/VOverlay.java4
-rw-r--r--client/src/com/vaadin/client/ui/VPanel.java1
-rw-r--r--client/src/com/vaadin/client/ui/VScrollTable.java164
-rw-r--r--client/src/com/vaadin/client/ui/VTabsheet.java51
-rw-r--r--client/src/com/vaadin/client/ui/VTreeTable.java2
-rw-r--r--client/src/com/vaadin/client/ui/VUpload.java7
-rw-r--r--client/src/com/vaadin/client/ui/VWindow.java7
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java8
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java2
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java1
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java1
-rw-r--r--client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java52
-rw-r--r--client/src/com/vaadin/client/ui/dd/VIsOverId.java3
-rw-r--r--client/src/com/vaadin/client/ui/dd/VItemIdIs.java2
-rw-r--r--client/src/com/vaadin/client/ui/label/LabelConnector.java6
-rw-r--r--client/src/com/vaadin/client/ui/orderedlayout/Slot.java51
-rw-r--r--client/src/com/vaadin/client/ui/table/TableConnector.java6
-rw-r--r--client/src/com/vaadin/client/ui/tree/TreeConnector.java20
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java2
-rw-r--r--client/src/com/vaadin/client/ui/upload/UploadConnector.java14
58 files changed, 4241 insertions, 1410 deletions
diff --git a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml
index 3aba1f6fee..2719493853 100755
--- a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml
+++ b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml
@@ -10,4 +10,8 @@
<entry-point class="com.vaadin.client.ApplicationConfiguration" />
+ <!-- Since 7.2. Compile all permutations (browser support) into one Javascript
+ file. Speeds up compilation and does not make the Javascript significantly
+ larger. -->
+ <collapse-all-properties />
</module>
diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml
index a1dca07a1c..d4eb454e86 100644
--- a/client/src/com/vaadin/Vaadin.gwt.xml
+++ b/client/src/com/vaadin/Vaadin.gwt.xml
@@ -59,6 +59,7 @@
<!-- Use the new cross site linker to get a nocache.js without document.write -->
<add-linker name="xsiframe" />
+ <extend-property name="user.agent" values="opera" />
<!-- Remove IE6/IE7 permutation as they are not supported -->
<set-property name="user.agent" value="ie8,ie9,ie10,gecko1_8,safari,opera" />
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java
index 7a70080c7e..1a5b0d836f 100644
--- a/client/src/com/vaadin/client/ApplicationConfiguration.java
+++ b/client/src/com/vaadin/client/ApplicationConfiguration.java
@@ -41,6 +41,7 @@ import com.vaadin.client.debug.internal.LogSection;
import com.vaadin.client.debug.internal.NetworkSection;
import com.vaadin.client.debug.internal.ProfilerSection;
import com.vaadin.client.debug.internal.Section;
+import com.vaadin.client.debug.internal.TestBenchSection;
import com.vaadin.client.debug.internal.VDebugWindow;
import com.vaadin.client.metadata.BundleLoadCallback;
import com.vaadin.client.metadata.ConnectorBundleLoader;
@@ -511,6 +512,30 @@ public class ApplicationConfiguration implements EntryPoint {
}
}
+ /**
+ * Returns all tags for given class. Tags are used in
+ * {@link ApplicationConfiguration} to keep track of different classes and
+ * their hierarchy
+ *
+ * @since 7.2
+ * @param classname
+ * name of class which tags we want
+ * @return Integer array of tags pointing to this classname
+ */
+ public Integer[] getTagsForServerSideClassName(String classname) {
+ List<Integer> tags = new ArrayList<Integer>();
+
+ for (Map.Entry<Integer, String> entry : tagToServerSideClassName
+ .entrySet()) {
+ if (classname.equals(entry.getValue())) {
+ tags.add(entry.getKey());
+ }
+ }
+
+ Integer[] out = new Integer[tags.size()];
+ return tags.toArray(out);
+ }
+
public Integer getParentTag(int tag) {
return componentInheritanceMap.get(tag);
}
@@ -595,6 +620,7 @@ public class ApplicationConfiguration implements EntryPoint {
window.addSection((Section) GWT.create(InfoSection.class));
window.addSection((Section) GWT.create(HierarchySection.class));
window.addSection((Section) GWT.create(NetworkSection.class));
+ window.addSection((Section) GWT.create(TestBenchSection.class));
if (Profiler.isEnabled()) {
window.addSection((Section) GWT.create(ProfilerSection.class));
}
@@ -760,5 +786,4 @@ public class ApplicationConfiguration implements EntryPoint {
private static final Logger getLogger() {
return Logger.getLogger(ApplicationConfiguration.class.getName());
}
-
}
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index da41543894..b84d8a376f 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright 2000-2013 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -65,16 +65,17 @@ import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConfiguration.ErrorMessage;
-import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadListener;
import com.vaadin.client.communication.HasJavaScriptConnectorHelper;
+import com.vaadin.client.communication.Heartbeat;
import com.vaadin.client.communication.JavaScriptMethodInvocation;
import com.vaadin.client.communication.JsonDecoder;
import com.vaadin.client.communication.JsonEncoder;
import com.vaadin.client.communication.PushConnection;
import com.vaadin.client.communication.RpcManager;
import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.componentlocator.ComponentLocator;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.client.metadata.ConnectorBundleLoader;
import com.vaadin.client.metadata.Method;
@@ -363,7 +364,7 @@ public class ApplicationConnection {
*
* To listen for the event add a {@link ApplicationStoppedHandler} by
* invoking
- * {@link ApplicationConnection#addHandler(ApplicationStoppedEvent.Type, ApplicationStoppedHandler)}
+ * {@link ApplicationConnection#addHandler(ApplicationConnection.ApplicationStoppedEvent.Type, ApplicationStoppedHandler)}
* to the {@link ApplicationConnection}
*
* @since 7.1.8
@@ -428,6 +429,8 @@ public class ApplicationConnection {
private VLoadingIndicator loadingIndicator;
+ private Heartbeat heartbeat = GWT.create(Heartbeat.class);
+
public static class MultiStepDuration extends Duration {
private int previousStep = elapsedMillis();
@@ -490,7 +493,7 @@ public class ApplicationConnection {
getLoadingIndicator().show();
- scheduleHeartbeat();
+ heartbeat.init(this);
Window.addWindowClosingHandler(new ClosingHandler() {
@Override
@@ -547,38 +550,47 @@ public class ApplicationConnection {
private native void initializeTestbenchHooks(
ComponentLocator componentLocator, String TTAppId)
/*-{
- var ap = this;
- var client = {};
- client.isActive = $entry(function() {
- return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()()
- || ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()();
- });
- var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()();
- if (vi) {
- client.getVersionInfo = function() {
- return vi;
- }
- }
+ var ap = this;
+ var client = {};
+ client.isActive = $entry(function() {
+ return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()()
+ || ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()();
+ });
+ var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()();
+ if (vi) {
+ client.getVersionInfo = function() {
+ return vi;
+ }
+ }
- client.getProfilingData = $entry(function() {
- var pd = [
- ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime,
+ client.getProfilingData = $entry(function() {
+ var pd = [
+ ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime,
ap.@com.vaadin.client.ApplicationConnection::totalProcessingTime
- ];
- pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo);
- pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime;
- return pd;
- });
+ ];
+ pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo);
+ pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime;
+ return pd;
+ });
- client.getElementByPath = $entry(function(id) {
- return componentLocator.@com.vaadin.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
- });
- client.getPathForElement = $entry(function(element) {
- return componentLocator.@com.vaadin.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
- });
- client.initializing = false;
+ client.getElementByPath = $entry(function(id) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
+ });
+ client.getElementByPathStartingAt = $entry(function(id, element) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/user/client/Element;)(id, element);
+ });
+ client.getElementsByPath = $entry(function(id) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id);
+ });
+ client.getElementsByPathStartingAt = $entry(function(id, element) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/user/client/Element;)(id, element);
+ });
+ client.getPathForElement = $entry(function(element) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
+ });
+ client.initializing = false;
- $wnd.vaadin.clients[TTAppId] = client;
+ $wnd.vaadin.clients[TTAppId] = client;
}-*/;
private static native final int calculateBootstrapTime()
@@ -1460,6 +1472,9 @@ public class ApplicationConnection {
if (meta.containsKey("timedRedirect")) {
final ValueMap timedRedirect = meta
.getValueMap("timedRedirect");
+ if (redirectTimer != null) {
+ redirectTimer.cancel();
+ }
redirectTimer = new Timer() {
@Override
public void run() {
@@ -3311,20 +3326,11 @@ public class ApplicationConnection {
* interval elapses if the interval is a positive number. Otherwise, does
* nothing.
*
- * @see #sendHeartbeat()
- * @see ApplicationConfiguration#getHeartbeatInterval()
+ * @deprecated as of 7.2, use {@link Heartbeat#schedule()} instead
*/
+ @Deprecated
protected void scheduleHeartbeat() {
- final int interval = getConfiguration().getHeartbeatInterval();
- if (interval > 0) {
- VConsole.log("Scheduling heartbeat in " + interval + " seconds");
- new Timer() {
- @Override
- public void run() {
- sendHeartbeat();
- }
- }.schedule(interval * 1000);
- }
+ heartbeat.schedule();
}
/**
@@ -3333,51 +3339,12 @@ public class ApplicationConnection {
* Heartbeat requests are used to inform the server that the client-side is
* still alive. If the client page is closed or the connection lost, the
* server will eventually close the inactive UI.
- * <p>
- * <b>TODO</b>: Improved error handling, like in doUidlRequest().
*
- * @see #scheduleHeartbeat()
+ * @deprecated as of 7.2, use {@link Heartbeat#send()} instead
*/
+ @Deprecated
protected void sendHeartbeat() {
- final String uri = addGetParameters(
- translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
- + ApplicationConstants.HEARTBEAT_PATH + '/'),
- UIConstants.UI_ID_PARAMETER + "="
- + getConfiguration().getUIId());
-
- final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
-
- final RequestCallback callback = new RequestCallback() {
-
- @Override
- public void onResponseReceived(Request request, Response response) {
- int status = response.getStatusCode();
- if (status == Response.SC_OK) {
- // TODO Permit retry in some error situations
- VConsole.log("Heartbeat response OK");
- scheduleHeartbeat();
- } else if (status == Response.SC_GONE) {
- showSessionExpiredError(null);
- } else {
- VConsole.error("Failed sending heartbeat to server. Error code: "
- + status);
- }
- }
-
- @Override
- public void onError(Request request, Throwable exception) {
- VConsole.error("Exception sending heartbeat: " + exception);
- }
- };
-
- rb.setCallback(callback);
-
- try {
- VConsole.log("Sending heartbeat request...");
- rb.send();
- } catch (RequestException re) {
- callback.onError(null, re);
- }
+ heartbeat.send();
}
/**
diff --git a/client/src/com/vaadin/client/ComponentLocator.java b/client/src/com/vaadin/client/ComponentLocator.java
index af934470c2..ef7ccc3b65 100644
--- a/client/src/com/vaadin/client/ComponentLocator.java
+++ b/client/src/com/vaadin/client/ComponentLocator.java
@@ -15,706 +15,20 @@
*/
package com.vaadin.client;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VCssLayout;
-import com.vaadin.client.ui.VGridLayout;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.VTabsheetPanel;
-import com.vaadin.client.ui.VUI;
-import com.vaadin.client.ui.VWindow;
-import com.vaadin.client.ui.orderedlayout.Slot;
-import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout;
-import com.vaadin.client.ui.window.WindowConnector;
-import com.vaadin.shared.AbstractComponentState;
-import com.vaadin.shared.Connector;
-import com.vaadin.shared.communication.SharedState;
-
/**
* ComponentLocator provides methods for generating a String locator for a given
* DOM element and for locating a DOM element using a String locator.
+ *
+ * @since 5.4
+ * @deprecated Moved to com.vaadin.client.componentlocator.ComponentLocator
*/
-public class ComponentLocator {
-
- /**
- * Separator used in the String locator between a parent and a child widget.
- */
- private static final String PARENTCHILD_SEPARATOR = "/";
-
- /**
- * Separator used in the String locator between the part identifying the
- * containing widget and the part identifying the target element within the
- * widget.
- */
- private static final String SUBPART_SEPARATOR = "#";
-
- /**
- * String that identifies the root panel when appearing first in the String
- * locator.
- */
- private static final String ROOT_ID = "Root";
-
- /**
- * Reference to ApplicationConnection instance.
- */
- private ApplicationConnection client;
-
+public class ComponentLocator extends com.vaadin.client.componentlocator.ComponentLocator {
/**
* Construct a ComponentLocator for the given ApplicationConnection.
- *
- * @param client
- * ApplicationConnection instance for the application.
+ *
+ * @param client ApplicationConnection instance for the application.
*/
public ComponentLocator(ApplicationConnection client) {
- this.client = client;
- }
-
- /**
- * Generates a String locator which uniquely identifies the target element.
- * The {@link #getElementByPath(String)} method can be used for the inverse
- * operation, i.e. locating an element based on the return value from this
- * method.
- * <p>
- * Note that getElementByPath(getPathForElement(element)) == element is not
- * always true as {@link #getPathForElement(Element)} can return a path to
- * another element if the widget determines an action on the other element
- * will give the same result as the action on the target element.
- * </p>
- *
- * @since 5.4
- * @param targetElement
- * The element to generate a path for.
- * @return A String locator that identifies the target element or null if a
- * String locator could not be created.
- */
- public String getPathForElement(Element targetElement) {
- String pid = null;
-
- targetElement = getElement(targetElement);
-
- Element e = targetElement;
-
- while (true) {
- pid = ConnectorMap.get(client).getConnectorId(e);
- if (pid != null) {
- break;
- }
-
- e = DOM.getParent(e);
- if (e == null) {
- break;
- }
- }
-
- Widget w = null;
- if (pid != null) {
- // If we found a Paintable then we use that as reference. We should
- // find the Paintable for all but very special cases (like
- // overlays).
- w = ((ComponentConnector) ConnectorMap.get(client)
- .getConnector(pid)).getWidget();
-
- /*
- * Still if the Paintable contains a widget that implements
- * SubPartAware, we want to use that as a reference
- */
- Widget targetParent = findParentWidget(targetElement, w);
- while (targetParent != w && targetParent != null) {
- if (targetParent instanceof SubPartAware) {
- /*
- * The targetParent widget is a child of the Paintable and
- * the first parent (of the targetElement) that implements
- * SubPartAware
- */
- w = targetParent;
- break;
- }
- targetParent = targetParent.getParent();
- }
- }
- if (w == null) {
- // Check if the element is part of a widget that is attached
- // directly to the root panel
- RootPanel rootPanel = RootPanel.get();
- int rootWidgetCount = rootPanel.getWidgetCount();
- for (int i = 0; i < rootWidgetCount; i++) {
- Widget rootWidget = rootPanel.getWidget(i);
- if (rootWidget.getElement().isOrHasChild(targetElement)) {
- // The target element is contained by this root widget
- w = findParentWidget(targetElement, rootWidget);
- break;
- }
- }
- if (w != null) {
- // We found a widget but we should still see if we find a
- // SubPartAware implementor (we cannot find the Paintable as
- // there is no link from VOverlay to its paintable/owner).
- Widget subPartAwareWidget = findSubPartAwareParentWidget(w);
- if (subPartAwareWidget != null) {
- w = subPartAwareWidget;
- }
- }
- }
-
- if (w == null) {
- // Containing widget not found
- return null;
- }
-
- // Determine the path for the target widget
- String path = getPathForWidget(w);
- if (path == null) {
- /*
- * No path could be determined for the target widget. Cannot create
- * a locator string.
- */
- return null;
- }
-
- // The parent check is a work around for Firefox 15 which fails to
- // compare elements properly (#9534)
- if (w.getElement() == targetElement) {
- /*
- * We are done if the target element is the root of the target
- * widget.
- */
- return path;
- } else if (w instanceof SubPartAware) {
- /*
- * If the widget can provide an identifier for the targetElement we
- * let it do that
- */
- String elementLocator = ((SubPartAware) w)
- .getSubPartName(targetElement);
- if (elementLocator != null) {
- return path + SUBPART_SEPARATOR + elementLocator;
- }
- }
- /*
- * If everything else fails we use the DOM path to identify the target
- * element
- */
- String domPath = getDOMPathForElement(targetElement, w.getElement());
- if (domPath == null) {
- return path;
- } else {
- return path + domPath;
- }
- }
-
- /**
- * Returns the element passed to the method. Or in case of Firefox 15,
- * returns the real element that is in the DOM instead of the element passed
- * to the method (which is the same element but not ==).
- *
- * @param targetElement
- * the element to return
- * @return the element passed to the method
- */
- private Element getElement(Element targetElement) {
- if (targetElement == null) {
- return null;
- }
-
- if (!BrowserInfo.get().isFirefox()) {
- return targetElement;
- }
-
- if (BrowserInfo.get().getBrowserMajorVersion() != 15) {
- return targetElement;
- }
-
- // Firefox 15, you make me sad
- if (targetElement.getNextSibling() != null) {
- return (Element) targetElement.getNextSibling()
- .getPreviousSibling();
- }
- if (targetElement.getPreviousSibling() != null) {
- return (Element) targetElement.getPreviousSibling()
- .getNextSibling();
- }
- // No siblings so this is the only child
- return (Element) targetElement.getParentNode().getChild(0);
- }
-
- /**
- * Finds the first widget in the hierarchy (moving upwards) that implements
- * SubPartAware. Returns the SubPartAware implementor or null if none is
- * found.
- *
- * @param w
- * The widget to start from. This is returned if it implements
- * SubPartAware.
- * @return The first widget (upwards in hierarchy) that implements
- * SubPartAware or null
- */
- private Widget findSubPartAwareParentWidget(Widget w) {
-
- while (w != null) {
- if (w instanceof SubPartAware) {
- return w;
- }
- w = w.getParent();
- }
- return null;
- }
-
- /**
- * Returns the first widget found when going from {@code targetElement}
- * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
- * parent of {@code targetElement}.
- *
- * @param targetElement
- * @param ancestorWidget
- * @return The widget whose root element is a parent of
- * {@code targetElement}.
- */
- private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
- /*
- * As we cannot resolve Widgets from the element we start from the
- * widget and move downwards to the correct child widget, as long as we
- * find one.
- */
- if (ancestorWidget instanceof HasWidgets) {
- for (Widget w : ((HasWidgets) ancestorWidget)) {
- if (w.getElement().isOrHasChild(targetElement)) {
- return findParentWidget(targetElement, w);
- }
- }
- }
-
- // No children found, this is it
- return ancestorWidget;
- }
-
- /**
- * Locates an element based on a DOM path and a base element.
- *
- * @param baseElement
- * The base element which the path is relative to
- * @param path
- * String locator (consisting of domChild[x] parts) that
- * identifies the element
- * @return The element identified by path, relative to baseElement or null
- * if the element could not be found.
- */
- private Element getElementByDOMPath(Element baseElement, String path) {
- String parts[] = path.split(PARENTCHILD_SEPARATOR);
- Element element = baseElement;
-
- for (String part : parts) {
- if (part.startsWith("domChild[")) {
- String childIndexString = part.substring("domChild[".length(),
- part.length() - 1);
-
- if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) {
- if (element.hasChildNodes()) {
- Element e = element.getFirstChildElement().cast();
- String cn = e.getClassName();
- if (cn != null
- && (cn.equals("v-expand") || cn
- .contains("v-has-caption"))) {
- element = e;
- }
- }
- }
-
- try {
- int childIndex = Integer.parseInt(childIndexString);
- element = DOM.getChild(element, childIndex);
- } catch (Exception e) {
- return null;
- }
-
- if (element == null) {
- return null;
- }
-
- }
- }
-
- return element;
+ super(client);
}
-
- /**
- * Generates a String locator using domChild[x] parts for the element
- * relative to the baseElement.
- *
- * @param element
- * The target element
- * @param baseElement
- * The starting point for the locator. The generated path is
- * relative to this element.
- * @return A String locator that can be used to locate the target element
- * using {@link #getElementByDOMPath(Element, String)} or null if
- * the locator String cannot be created.
- */
- private String getDOMPathForElement(Element element, Element baseElement) {
- Element e = element;
- String path = "";
- while (true) {
- int childIndex = -1;
- Element siblingIterator = e;
- while (siblingIterator != null) {
- childIndex++;
- siblingIterator = siblingIterator.getPreviousSiblingElement()
- .cast();
- }
-
- path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
- + path;
-
- JavaScriptObject parent = e.getParentElement();
- if (parent == null) {
- return null;
- }
- // The parent check is a work around for Firefox 15 which fails to
- // compare elements properly (#9534)
- if (parent == baseElement) {
- break;
- }
-
- e = parent.cast();
- }
-
- return path;
- }
-
- /**
- * Locates an element using a String locator (path) which identifies a DOM
- * element. The {@link #getPathForElement(Element)} method can be used for
- * the inverse operation, i.e. generating a string expression for a DOM
- * element.
- *
- * @since 5.4
- * @param path
- * The String locater which identifies the target element.
- * @return The DOM element identified by {@code path} or null if the element
- * could not be located.
- */
- public Element getElementByPath(String path) {
- /*
- * Path is of type "targetWidgetPath#componentPart" or
- * "targetWidgetPath".
- */
- String parts[] = path.split(SUBPART_SEPARATOR, 2);
- String widgetPath = parts[0];
- Widget w = getWidgetFromPath(widgetPath);
- if (w == null || !Util.isAttachedAndDisplayed(w)) {
- return null;
- }
-
- if (parts.length == 1) {
- int pos = widgetPath.indexOf("domChild");
- if (pos == -1) {
- return w.getElement();
- }
-
- // Contains dom reference to a sub element of the widget
- String subPath = widgetPath.substring(pos);
- return getElementByDOMPath(w.getElement(), subPath);
- } else if (parts.length == 2) {
- if (w instanceof SubPartAware) {
- return ((SubPartAware) w).getSubPartElement(parts[1]);
- }
- }
-
- return null;
- }
-
- /**
- * Creates a locator String for the given widget. The path can be used to
- * locate the widget using {@link #getWidgetFromPath(String)}.
- *
- * Returns null if no path can be determined for the widget or if the widget
- * is null.
- *
- * @param w
- * The target widget
- * @return A String locator for the widget
- */
- private String getPathForWidget(Widget w) {
- if (w == null) {
- return null;
- }
- String elementId = w.getElement().getId();
- if (elementId != null && !elementId.isEmpty()
- && !elementId.startsWith("gwt-uid-")) {
- // Use PID_S+id if the user has set an id but do not use it for auto
- // generated id:s as these might not be consistent
- return "PID_S" + elementId;
- } else if (w instanceof VUI) {
- return "";
- } else if (w instanceof VWindow) {
- Connector windowConnector = ConnectorMap.get(client)
- .getConnector(w);
- List<WindowConnector> subWindowList = client.getUIConnector()
- .getSubWindows();
- int indexOfSubWindow = subWindowList.indexOf(windowConnector);
- return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
- } else if (w instanceof RootPanel) {
- return ROOT_ID;
- }
-
- Widget parent = w.getParent();
-
- String basePath = getPathForWidget(parent);
- if (basePath == null) {
- return null;
- }
- String simpleName = Util.getSimpleName(w);
-
- /*
- * Check if the parent implements Iterable. At least VPopupView does not
- * implement HasWdgets so we cannot check for that.
- */
- if (!(parent instanceof Iterable<?>)) {
- // Parent does not implement Iterable so we cannot find out which
- // child this is
- return null;
- }
-
- Iterator<?> i = ((Iterable<?>) parent).iterator();
- int pos = 0;
- while (i.hasNext()) {
- Object child = i.next();
- if (child == w) {
- return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
- + pos + "]";
- }
- String simpleName2 = Util.getSimpleName(child);
- if (simpleName.equals(simpleName2)) {
- pos++;
- }
- }
-
- return null;
- }
-
- /**
- * Locates the widget based on a String locator.
- *
- * @param path
- * The String locator that identifies the widget.
- * @return The Widget identified by the String locator or null if the widget
- * could not be identified.
- */
- private Widget getWidgetFromPath(String path) {
- Widget w = null;
- String parts[] = path.split(PARENTCHILD_SEPARATOR);
-
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
-
- if (part.equals(ROOT_ID)) {
- w = RootPanel.get();
- } else if (part.equals("")) {
- w = client.getUIConnector().getWidget();
- } else if (w == null) {
- String id = part;
- // Must be old static pid (PID_S*)
- ServerConnector connector = ConnectorMap.get(client)
- .getConnector(id);
- if (connector == null) {
- // Lookup by component id
- // TODO Optimize this
- connector = findConnectorById(client.getUIConnector(),
- id.substring(5));
- }
-
- if (connector instanceof ComponentConnector) {
- w = ((ComponentConnector) connector).getWidget();
- } else {
- // Not found
- return null;
- }
- } else if (part.startsWith("domChild[")) {
- // The target widget has been found and the rest identifies the
- // element
- break;
- } else if (w instanceof Iterable) {
- // W identifies a widget that contains other widgets, as it
- // should. Try to locate the child
- Iterable<?> parent = (Iterable<?>) w;
-
- // Part is of type "VVerticalLayout[0]", split this into
- // VVerticalLayout and 0
- String[] split = part.split("\\[", 2);
- String widgetClassName = split[0];
- String indexString = split[1].substring(0,
- split[1].length() - 1);
- int widgetPosition = Integer.parseInt(indexString);
-
- // AbsolutePanel in GridLayout has been removed -> skip it
- if (w instanceof VGridLayout
- && "AbsolutePanel".equals(widgetClassName)) {
- continue;
- }
-
- // FlowPane in CSSLayout has been removed -> skip it
- if (w instanceof VCssLayout
- && "VCssLayout$FlowPane".equals(widgetClassName)) {
- continue;
- }
-
- // ChildComponentContainer and VOrderedLayout$Slot have been
- // replaced with Slot
- if (w instanceof VAbstractOrderedLayout
- && ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot"
- .equals(widgetClassName))) {
- widgetClassName = "Slot";
- }
-
- if (w instanceof VTabsheetPanel && widgetPosition != 0) {
- // TabSheetPanel now only contains 1 connector => the index
- // is always 0 which indicates the widget in the active tab
- widgetPosition = 0;
- }
- if (w instanceof VOverlay
- && "VCalendarPanel".equals(widgetClassName)) {
- // Vaadin 7.1 adds a wrapper for datefield popups
- parent = (Iterable<?>) ((Iterable) parent).iterator()
- .next();
- }
- /*
- * The new grid and ordered layotus do not contain
- * ChildComponentContainer widgets. This is instead simulated by
- * constructing a path step that would find the desired widget
- * from the layout and injecting it as the next search step
- * (which would originally have found the widget inside the
- * ChildComponentContainer)
- */
- if ((w instanceof VGridLayout)
- && "ChildComponentContainer".equals(widgetClassName)
- && i + 1 < parts.length) {
-
- HasWidgets layout = (HasWidgets) w;
-
- String nextPart = parts[i + 1];
- String[] nextSplit = nextPart.split("\\[", 2);
- String nextWidgetClassName = nextSplit[0];
-
- // Find the n:th child and count the number of children with
- // the same type before it
- int nextIndex = 0;
- for (Widget child : layout) {
- boolean matchingType = nextWidgetClassName.equals(Util
- .getSimpleName(child));
- if (matchingType && widgetPosition == 0) {
- // This is the n:th child that we looked for
- break;
- } else if (widgetPosition < 0) {
- // Error if we're past the desired position without
- // a match
- return null;
- } else if (matchingType) {
- // If this was another child of the expected type,
- // increase the count for the next step
- nextIndex++;
- }
-
- // Don't count captions
- if (!(child instanceof VCaption)) {
- widgetPosition--;
- }
- }
-
- // Advance to the next step, this time checking for the
- // actual child widget
- parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']';
- continue;
- }
-
- // Locate the child
- Iterator<? extends Widget> iterator;
-
- /*
- * VWindow and VContextMenu workarounds for backwards
- * compatibility
- */
- if (widgetClassName.equals("VWindow")) {
- List<WindowConnector> windows = client.getUIConnector()
- .getSubWindows();
- List<VWindow> windowWidgets = new ArrayList<VWindow>(
- windows.size());
- for (WindowConnector wc : windows) {
- windowWidgets.add(wc.getWidget());
- }
- iterator = windowWidgets.iterator();
- } else if (widgetClassName.equals("VContextMenu")) {
- return client.getContextMenu();
- } else {
- iterator = (Iterator<? extends Widget>) parent.iterator();
- }
-
- boolean ok = false;
-
- // Find the widgetPosition:th child of type "widgetClassName"
- while (iterator.hasNext()) {
-
- Widget child = iterator.next();
- String simpleName2 = Util.getSimpleName(child);
-
- if (!widgetClassName.equals(simpleName2)
- && child instanceof Slot) {
- /*
- * Support legacy tests without any selector for the
- * Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by
- * directly checking the stuff inside the slot
- */
- child = ((Slot) child).getWidget();
- simpleName2 = Util.getSimpleName(child);
- }
-
- if (widgetClassName.equals(simpleName2)) {
- if (widgetPosition == 0) {
- w = child;
- ok = true;
- break;
- }
- widgetPosition--;
-
- }
- }
-
- if (!ok) {
- // Did not find the child
- return null;
- }
- } else {
- // W identifies something that is not a "HasWidgets". This
- // should not happen as all widget containers should implement
- // HasWidgets.
- return null;
- }
- }
-
- return w;
- }
-
- private ServerConnector findConnectorById(ServerConnector root, String id) {
- SharedState state = root.getState();
- if (state instanceof AbstractComponentState
- && id.equals(((AbstractComponentState) state).id)) {
- return root;
- }
- for (ServerConnector child : root.getChildren()) {
- ServerConnector found = findConnectorById(child, id);
- if (found != null) {
- return found;
- }
- }
-
- return null;
- }
-
}
diff --git a/client/src/com/vaadin/client/ConnectorMap.java b/client/src/com/vaadin/client/ConnectorMap.java
index 810f12824a..c2f1eda21d 100644
--- a/client/src/com/vaadin/client/ConnectorMap.java
+++ b/client/src/com/vaadin/client/ConnectorMap.java
@@ -116,7 +116,7 @@ public class ConnectorMap {
* no connector was found
*/
public ComponentConnector getConnector(Widget widget) {
- return getConnector(widget.getElement());
+ return widget == null ? null : getConnector(widget.getElement());
}
public void registerConnector(String id, ServerConnector connector) {
diff --git a/client/src/com/vaadin/client/Profiler.java b/client/src/com/vaadin/client/Profiler.java
index 083f2559b1..cfce59b08b 100644
--- a/client/src/com/vaadin/client/Profiler.java
+++ b/client/src/com/vaadin/client/Profiler.java
@@ -297,10 +297,6 @@ public class Profiler {
if (isEnabled()) {
double now = Duration.currentTimeMillis();
- StringBuilder stringBuilder = new StringBuilder(
- "Time since window.performance.timing events");
- SimpleTree tree = new SimpleTree(stringBuilder.toString());
-
String[] keys = new String[] { "navigationStart",
"unloadEventStart", "unloadEventEnd", "redirectStart",
"redirectEnd", "fetchStart", "domainLookupStart",
diff --git a/client/src/com/vaadin/client/SimpleTree.java b/client/src/com/vaadin/client/SimpleTree.java
index 7370496cb8..edfa23fb13 100644
--- a/client/src/com/vaadin/client/SimpleTree.java
+++ b/client/src/com/vaadin/client/SimpleTree.java
@@ -116,6 +116,14 @@ public class SimpleTree extends ComplexPanel implements HasDoubleClickHandlers {
}
}
+ public boolean isOpen() {
+ return "-".equals(handle.getInnerHTML());
+ }
+
+ public String getCaption() {
+ return text.getInnerText();
+ }
+
public SimpleTree(String caption) {
this();
setText(caption);
diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java
index 8972670232..edbb40e86c 100644
--- a/client/src/com/vaadin/client/Util.java
+++ b/client/src/com/vaadin/client/Util.java
@@ -855,6 +855,7 @@ public class Util {
* @param class1
* the Widget type to seek for
*/
+ @SuppressWarnings("unchecked")
public static <T> T findWidget(Element element,
Class<? extends Widget> class1) {
if (element != null) {
@@ -866,7 +867,7 @@ public class Util {
element = (Element) element.getParentElement();
}
}
- if (eventListener != null) {
+ if (eventListener instanceof Widget) {
/*
* Then find the first widget of type class1 from widget
* hierarchy
diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
index c9b9e46cd5..9eb2b881bb 100644
--- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
+++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
@@ -344,6 +344,14 @@ public class AtmospherePushConnection implements PushConnection {
state = State.CONNECT_PENDING;
}
+ protected void onClientTimeout(AtmosphereResponse response) {
+ state = State.DISCONNECTED;
+ errorHandler
+ .onError(
+ "Client unexpectedly disconnected. Ensure client timeout is disabled.",
+ -1);
+ }
+
protected void onReconnect(JavaScriptObject request,
final AtmosphereResponse response) {
if (state == State.CONNECTED) {
@@ -438,6 +446,7 @@ public class AtmospherePushConnection implements PushConnection {
fallbackTransport: 'streaming',
contentType: 'application/json; charset=UTF-8',
reconnectInterval: 5000,
+ timeout: -1,
maxReconnectOnClose: 10000000,
trackMessageLength: true,
enableProtocol: false,
@@ -472,6 +481,9 @@ public class AtmospherePushConnection implements PushConnection {
config.onReconnect = $entry(function(request, response) {
self.@com.vaadin.client.communication.AtmospherePushConnection::onReconnect(*)(request, response);
});
+ config.onClientTimeout = $entry(function(request) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onClientTimeout(*)(request);
+ });
return $wnd.jQueryVaadin.atmosphere.subscribe(config);
}-*/;
diff --git a/client/src/com/vaadin/client/communication/Date_Serializer.java b/client/src/com/vaadin/client/communication/Date_Serializer.java
new file mode 100644
index 0000000000..c6eb7af188
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/Date_Serializer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.communication;
+
+import java.util.Date;
+
+import com.google.gwt.json.client.JSONNumber;
+import com.google.gwt.json.client.JSONValue;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.metadata.Type;
+
+/**
+ * Client side serializer/deserializer for java.util.Date
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class Date_Serializer implements JSONSerializer<Date> {
+
+ @Override
+ public Date deserialize(Type type, JSONValue jsonValue,
+ ApplicationConnection connection) {
+ return new Date((long) ((JSONNumber) jsonValue).doubleValue());
+ }
+
+ @Override
+ public JSONValue serialize(Date value, ApplicationConnection connection) {
+ return new JSONNumber(value.getTime());
+ }
+
+}
diff --git a/client/src/com/vaadin/client/communication/Heartbeat.java b/client/src/com/vaadin/client/communication/Heartbeat.java
new file mode 100644
index 0000000000..4b80827127
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/Heartbeat.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.communication;
+
+import java.util.logging.Logger;
+
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.user.client.Timer;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+/**
+ * Handles sending of heartbeats to the server and reacting to the response
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class Heartbeat {
+
+ private int interval = -1;
+ private Timer timer = new Timer() {
+ @Override
+ public void run() {
+ send();
+ }
+ };
+
+ private ApplicationConnection connection;
+
+ private static Logger getLogger() {
+ return Logger.getLogger(Heartbeat.class.getName());
+ }
+
+ /**
+ * Initializes the heartbeat for the given application connection
+ *
+ * @param connection
+ * the connection
+ */
+ public void init(ApplicationConnection connection) {
+ this.connection = connection;
+ interval = connection.getConfiguration().getHeartbeatInterval();
+ setInterval(interval);
+ schedule();
+
+ connection.addHandler(
+ ApplicationConnection.ApplicationStoppedEvent.TYPE,
+ new ApplicationConnection.ApplicationStoppedHandler() {
+
+ @Override
+ public void onApplicationStopped(
+ ApplicationStoppedEvent event) {
+ setInterval(-1);
+ schedule();
+ }
+ });
+
+ }
+
+ /**
+ * Sends a heartbeat to the server
+ */
+ public void send() {
+ final String uri = ApplicationConnection.addGetParameters(
+ getConnection().translateVaadinUri(
+ ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ApplicationConstants.HEARTBEAT_PATH + '/'),
+ UIConstants.UI_ID_PARAMETER + "="
+ + getConnection().getConfiguration().getUIId());
+
+ final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
+
+ final RequestCallback callback = new RequestCallback() {
+
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ int status = response.getStatusCode();
+ if (status == Response.SC_OK) {
+ // TODO Permit retry in some error situations
+ getLogger().fine("Heartbeat response OK");
+ schedule();
+ } else if (status == Response.SC_GONE) {
+ // FIXME This should really do something else like send an
+ // event
+ getConnection().showSessionExpiredError(null);
+ } else {
+ getLogger().warning(
+ "Failed sending heartbeat to server. Error code: "
+ + status);
+ }
+ }
+
+ @Override
+ public void onError(Request request, Throwable exception) {
+ getLogger().severe("Exception sending heartbeat: " + exception);
+ }
+ };
+
+ rb.setCallback(callback);
+
+ try {
+ getLogger().fine("Sending heartbeat request...");
+ rb.send();
+ } catch (RequestException re) {
+ callback.onError(null, re);
+ }
+
+ }
+
+ /**
+ * @return the interval at which heartbeat requests are sent
+ */
+ public int getInterval() {
+ return interval;
+ }
+
+ /**
+ * sets the interval at which heartbeat requests are sent
+ *
+ * @param interval
+ * the new interval
+ */
+ public void setInterval(int interval) {
+ this.interval = interval;
+ }
+
+ /**
+ * Updates the schedule of the heartbeat to match the set interval. A
+ * negative interval disables the heartbeat.
+ */
+ public void schedule() {
+ if (getInterval() > 0) {
+ getLogger()
+ .fine("Scheduling heartbeat in " + interval + " seconds");
+ timer.schedule(interval * 1000);
+ } else {
+ if (timer != null) {
+ getLogger().fine("Disabling heartbeat");
+ timer.cancel();
+ }
+ }
+
+ }
+
+ /**
+ * @return the application connection
+ */
+ protected ApplicationConnection getConnection() {
+ return connection;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/communication/JSONSerializer.java b/client/src/com/vaadin/client/communication/JSONSerializer.java
index e5829ece24..a4e78e503c 100644
--- a/client/src/com/vaadin/client/communication/JSONSerializer.java
+++ b/client/src/com/vaadin/client/communication/JSONSerializer.java
@@ -23,14 +23,17 @@ import com.vaadin.client.metadata.Type;
/**
* Implementors of this interface knows how to serialize an Object of a given
* type to JSON and how to deserialize the JSON back into an object.
- *
+ * <p>
* The {@link #serialize(Object, ApplicationConnection)} and
* {@link #deserialize(Type, JSONValue, ApplicationConnection)} methods must be
* symmetric so they can be chained and produce the original result (or an equal
* result).
- *
+ * <p>
* Each {@link JSONSerializer} implementation can handle an object of a single
* type - see {@link Type#findSerializer()}.
+ * <p>
+ * This is the client side interface, see
+ * com.vaadin.server.communication.JSONSerializer for the server side interface.
*
* @since 7.0
*/
diff --git a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java
new file mode 100644
index 0000000000..d2a89c00d5
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.ApplicationConnection;
+
+/**
+ * ComponentLocator provides methods for generating a String locator for a given
+ * DOM element and for locating a DOM element using a String locator.
+ * <p>
+ * The main use for this class is locating components for automated testing
+ * purposes.
+ *
+ * @since 7.2, moved from {@link com.vaadin.client.ComponentLocator}
+ */
+public class ComponentLocator {
+
+ private final List<LocatorStrategy> locatorStrategies;
+
+ /**
+ * Reference to ApplicationConnection instance.
+ */
+
+ private final ApplicationConnection client;
+
+ /**
+ * Construct a ComponentLocator for the given ApplicationConnection.
+ *
+ * @param client
+ * ApplicationConnection instance for the application.
+ */
+ public ComponentLocator(ApplicationConnection client) {
+ this.client = client;
+ locatorStrategies = Arrays.asList(new VaadinFinderLocatorStrategy(
+ client), new LegacyLocatorStrategy(client));
+ }
+
+ /**
+ * Generates a String locator which uniquely identifies the target element.
+ * The {@link #getElementByPath(String)} method can be used for the inverse
+ * operation, i.e. locating an element based on the return value from this
+ * method.
+ * <p>
+ * Note that getElementByPath(getPathForElement(element)) == element is not
+ * always true as #getPathForElement(Element) can return a path to another
+ * element if the widget determines an action on the other element will give
+ * the same result as the action on the target element.
+ * </p>
+ *
+ * @since 5.4
+ * @param targetElement
+ * The element to generate a path for.
+ * @return A String locator that identifies the target element or null if a
+ * String locator could not be created.
+ */
+ public String getPathForElement(Element targetElement) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ String path = strategy.getPathForElement(targetElement);
+ if (null != path) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Locates an element using a String locator (path) which identifies a DOM
+ * element. The {@link #getPathForElement(Element)} method can be used for
+ * the inverse operation, i.e. generating a string expression for a DOM
+ * element.
+ *
+ * @since 5.4
+ * @param path
+ * The String locator which identifies the target element.
+ * @return The DOM element identified by {@code path} or null if the element
+ * could not be located.
+ */
+ public Element getElementByPath(String path) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ if (strategy.validatePath(path)) {
+ Element element = strategy.getElementByPath(path);
+ if (null != element) {
+ return element;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Locates elements using a String locator (path) which identifies DOM
+ * elements.
+ *
+ * @since 7.2
+ * @param path
+ * The String locator which identifies target elements.
+ * @return The JavaScriptArray of DOM elements identified by {@code path} or
+ * empty array if elements could not be located.
+ */
+ public JsArray<Element> getElementsByPath(String path) {
+ JsArray<Element> jsElements = JavaScriptObject.createArray().cast();
+ for (LocatorStrategy strategy : locatorStrategies) {
+ if (strategy.validatePath(path)) {
+ List<Element> elements = strategy.getElementsByPath(path);
+ if (elements.size() > 0) {
+ for (Element e : elements) {
+ jsElements.push(e);
+ }
+ return jsElements;
+ }
+ }
+ }
+ return jsElements;
+ }
+
+ /**
+ * Locates elements using a String locator (path) which identifies DOM
+ * elements. The path starts from the specified root element.
+ *
+ * @see #getElementByPath(String)
+ *
+ * @since 7.2
+ * @param path
+ * The path of elements to be found
+ * @param root
+ * The root element where the path is anchored
+ * @return The JavaScriptArray of DOM elements identified by {@code path} or
+ * empty array if elements could not be located.
+ */
+ public JsArray<Element> getElementsByPathStartingAt(String path,
+ Element root) {
+ JsArray<Element> jsElements = JavaScriptObject.createArray().cast();
+ for (LocatorStrategy strategy : locatorStrategies) {
+ if (strategy.validatePath(path)) {
+ List<Element> elements = strategy.getElementsByPathStartingAt(
+ path, root);
+ if (elements.size() > 0) {
+ for (Element e : elements) {
+ jsElements.push(e);
+ }
+ return jsElements;
+ }
+ }
+ }
+ return jsElements;
+ }
+
+ /**
+ * Locates an element using a String locator (path) which identifies a DOM
+ * element. The path starts from the specified root element.
+ *
+ * @see #getElementByPath(String)
+ *
+ * @param path
+ * The path of the element to be found
+ * @param root
+ * The root element where the path is anchored
+ * @return The DOM element identified by {@code path} or null if the element
+ * could not be located.
+ */
+ public Element getElementByPathStartingAt(String path, Element root) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ if (strategy.validatePath(path)) {
+ Element element = strategy.getElementByPathStartingAt(path,
+ root);
+ if (null != element) {
+ return element;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link ApplicationConnection} used by this locator.
+ * <p>
+ * This method is primarily for internal use by the framework.
+ *
+ * @return the application connection
+ */
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ /**
+ * Check if a given selector is valid for LegacyLocatorStrategy.
+ *
+ * @param path
+ * Vaadin selector path
+ * @return true if passes path validation with LegacyLocatorStrategy
+ */
+ public boolean isValidForLegacyLocator(String path) {
+ for (LocatorStrategy ls : locatorStrategies) {
+ if (ls instanceof LegacyLocatorStrategy) {
+ return ls.validatePath(path);
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java
new file mode 100644
index 0000000000..2e9d0a16d0
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.SubPartAware;
+import com.vaadin.client.ui.VCssLayout;
+import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VOverlay;
+import com.vaadin.client.ui.VTabsheetPanel;
+import com.vaadin.client.ui.VUI;
+import com.vaadin.client.ui.VWindow;
+import com.vaadin.client.ui.orderedlayout.Slot;
+import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout;
+import com.vaadin.client.ui.window.WindowConnector;
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * The LegacyLocatorStrategy class handles the legacy locator syntax that was
+ * introduced in version 5.4 of the framework. The legacy locator strategy is
+ * always used if no other strategy claims responsibility for a locator string.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class LegacyLocatorStrategy implements LocatorStrategy {
+
+ /**
+ * Separator used in the String locator between a parent and a child widget.
+ */
+ static final String PARENTCHILD_SEPARATOR = "/";
+ /**
+ * Separator used in the String locator between the part identifying the
+ * containing widget and the part identifying the target element within the
+ * widget.
+ */
+ static final String SUBPART_SEPARATOR = "#";
+ /**
+ * String that identifies the root panel when appearing first in the String
+ * locator.
+ */
+ static final String ROOT_ID = "Root";
+
+ private final ApplicationConnection client;
+
+ private static final RegExp validSyntax = RegExp
+ .compile("^((\\w+::)?((PID_S)?\\w[-$_a-zA-Z0-9.' ]*)?)?(/[-$_a-zA-Z0-9]+\\[\\d+\\])*/?(#.*)?$");
+
+ public LegacyLocatorStrategy(ApplicationConnection clientConnection) {
+ client = clientConnection;
+ }
+
+ @Override
+ public boolean validatePath(String path) {
+ return validSyntax.test(path);
+ }
+
+ @Override
+ public String getPathForElement(Element targetElement) {
+ ComponentConnector connector = Util
+ .findPaintable(client, targetElement);
+
+ Widget w = null;
+ if (connector != null) {
+ // If we found a Paintable then we use that as reference. We should
+ // find the Paintable for all but very special cases (like
+ // overlays).
+ w = connector.getWidget();
+
+ /*
+ * Still if the Paintable contains a widget that implements
+ * SubPartAware, we want to use that as a reference
+ */
+ Widget targetParent = findParentWidget(targetElement, w);
+ while (targetParent != w && targetParent != null) {
+ if (targetParent instanceof SubPartAware) {
+ /*
+ * The targetParent widget is a child of the Paintable and
+ * the first parent (of the targetElement) that implements
+ * SubPartAware
+ */
+ w = targetParent;
+ break;
+ }
+ targetParent = targetParent.getParent();
+ }
+ }
+ if (w == null) {
+ // Check if the element is part of a widget that is attached
+ // directly to the root panel
+ RootPanel rootPanel = RootPanel.get();
+ int rootWidgetCount = rootPanel.getWidgetCount();
+ for (int i = 0; i < rootWidgetCount; i++) {
+ Widget rootWidget = rootPanel.getWidget(i);
+ if (rootWidget.getElement().isOrHasChild(targetElement)) {
+ // The target element is contained by this root widget
+ w = findParentWidget(targetElement, rootWidget);
+ break;
+ }
+ }
+ if (w != null) {
+ // We found a widget but we should still see if we find a
+ // SubPartAware implementor (we cannot find the Paintable as
+ // there is no link from VOverlay to its paintable/owner).
+ Widget subPartAwareWidget = findSubPartAwareParentWidget(w);
+ if (subPartAwareWidget != null) {
+ w = subPartAwareWidget;
+ }
+ }
+ }
+
+ if (w == null) {
+ // Containing widget not found
+ return null;
+ }
+
+ // Determine the path for the target widget
+ String path = getPathForWidget(w);
+ if (path == null) {
+ /*
+ * No path could be determined for the target widget. Cannot create
+ * a locator string.
+ */
+ return null;
+ }
+
+ // The parent check is a work around for Firefox 15 which fails to
+ // compare elements properly (#9534)
+ if (w.getElement() == targetElement) {
+ /*
+ * We are done if the target element is the root of the target
+ * widget.
+ */
+ return path;
+ } else if (w instanceof SubPartAware) {
+ /*
+ * If the widget can provide an identifier for the targetElement we
+ * let it do that
+ */
+ String elementLocator = ((SubPartAware) w)
+ .getSubPartName(targetElement);
+ if (elementLocator != null) {
+ return path + LegacyLocatorStrategy.SUBPART_SEPARATOR
+ + elementLocator;
+ }
+ }
+ /*
+ * If everything else fails we use the DOM path to identify the target
+ * element
+ */
+ String domPath = getDOMPathForElement(targetElement, w.getElement());
+ if (domPath == null) {
+ return path;
+ } else {
+ return path + domPath;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPath(String path) {
+ return getElementByPathStartingAt(path, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPathStartingAt(String path, Element baseElement) {
+ /*
+ * Path is of type "targetWidgetPath#componentPart" or
+ * "targetWidgetPath".
+ */
+ String parts[] = path.split(LegacyLocatorStrategy.SUBPART_SEPARATOR, 2);
+ String widgetPath = parts[0];
+
+ // Note that this only works if baseElement can be mapped to a
+ // widget to which the path is relative. Otherwise, the current
+ // implementation simply interprets the path as if baseElement was
+ // null.
+ Widget baseWidget = Util.findWidget(baseElement, null);
+
+ Widget w = getWidgetFromPath(widgetPath, baseWidget);
+ if (w == null || !Util.isAttachedAndDisplayed(w)) {
+ return null;
+ }
+ if (parts.length == 1) {
+ int pos = widgetPath.indexOf("domChild");
+ if (pos == -1) {
+ return w.getElement();
+ }
+
+ // Contains dom reference to a sub element of the widget
+ String subPath = widgetPath.substring(pos);
+ return getElementByDOMPath(w.getElement(), subPath);
+ } else if (parts.length == 2) {
+ if (w instanceof SubPartAware) {
+ return ((SubPartAware) w).getSubPartElement(parts[1]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Element> getElementsByPath(String path) {
+ // This type of search is not supported in LegacyLocator
+ List<Element> array = new ArrayList<Element>();
+ Element e = getElementByPath(path);
+ if (e != null) {
+ array.add(e);
+ }
+ return array;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Element> getElementsByPathStartingAt(String path, Element root) {
+ // This type of search is not supported in LegacyLocator
+ List<Element> array = new ArrayList<Element>();
+ Element e = getElementByPathStartingAt(path, root);
+ if (e != null) {
+ array.add(e);
+ }
+ return array;
+ }
+
+ /**
+ * Finds the first widget in the hierarchy (moving upwards) that implements
+ * SubPartAware. Returns the SubPartAware implementor or null if none is
+ * found.
+ *
+ * @param w
+ * The widget to start from. This is returned if it implements
+ * SubPartAware.
+ * @return The first widget (upwards in hierarchy) that implements
+ * SubPartAware or null
+ */
+ Widget findSubPartAwareParentWidget(Widget w) {
+
+ while (w != null) {
+ if (w instanceof SubPartAware) {
+ return w;
+ }
+ w = w.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first widget found when going from {@code targetElement}
+ * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
+ * parent of {@code targetElement}.
+ *
+ * @param targetElement
+ * @param ancestorWidget
+ * @return The widget whose root element is a parent of
+ * {@code targetElement}.
+ */
+ private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
+ /*
+ * As we cannot resolve Widgets from the element we start from the
+ * widget and move downwards to the correct child widget, as long as we
+ * find one.
+ */
+ if (ancestorWidget instanceof HasWidgets) {
+ for (Widget w : ((HasWidgets) ancestorWidget)) {
+ if (w.getElement().isOrHasChild(targetElement)) {
+ return findParentWidget(targetElement, w);
+ }
+ }
+ }
+
+ // No children found, this is it
+ return ancestorWidget;
+ }
+
+ /**
+ * Locates an element based on a DOM path and a base element.
+ *
+ * @param baseElement
+ * The base element which the path is relative to
+ * @param path
+ * String locator (consisting of domChild[x] parts) that
+ * identifies the element
+ * @return The element identified by path, relative to baseElement or null
+ * if the element could not be found.
+ */
+ private Element getElementByDOMPath(Element baseElement, String path) {
+ String parts[] = path.split(PARENTCHILD_SEPARATOR);
+ Element element = baseElement;
+
+ for (int i = 0, l = parts.length; i < l; ++i) {
+ String part = parts[i];
+ if (part.startsWith("domChild[")) {
+ String childIndexString = part.substring("domChild[".length(),
+ part.length() - 1);
+
+ if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) {
+ if (element.hasChildNodes()) {
+ Element e = element.getFirstChildElement().cast();
+ String cn = e.getClassName();
+ if (cn != null
+ && (cn.equals("v-expand") || cn
+ .contains("v-has-caption"))) {
+ element = e;
+ }
+ }
+ }
+
+ try {
+ int childIndex = Integer.parseInt(childIndexString);
+ element = DOM.getChild(element, childIndex);
+ } catch (Exception e) {
+ return null;
+ }
+
+ if (element == null) {
+ return null;
+ }
+
+ } else {
+
+ path = parts[i];
+ for (int j = i + 1; j < l; ++j) {
+ path += PARENTCHILD_SEPARATOR + parts[j];
+ }
+
+ return getElementByPathStartingAt(path, element);
+ }
+ }
+
+ return element;
+ }
+
+ /**
+ * Generates a String locator using domChild[x] parts for the element
+ * relative to the baseElement.
+ *
+ * @param element
+ * The target element
+ * @param baseElement
+ * The starting point for the locator. The generated path is
+ * relative to this element.
+ * @return A String locator that can be used to locate the target element
+ * using
+ * {@link #getElementByDOMPath(com.google.gwt.user.client.Element, String)}
+ * or null if the locator String cannot be created.
+ */
+ private String getDOMPathForElement(Element element, Element baseElement) {
+ Element e = element;
+ String path = "";
+ while (true) {
+ int childIndex = -1;
+ Element siblingIterator = e;
+ while (siblingIterator != null) {
+ childIndex++;
+ siblingIterator = siblingIterator.getPreviousSiblingElement()
+ .cast();
+ }
+
+ path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
+ + path;
+
+ JavaScriptObject parent = e.getParentElement();
+ if (parent == null) {
+ return null;
+ }
+ // The parent check is a work around for Firefox 15 which fails to
+ // compare elements properly (#9534)
+ if (parent == baseElement) {
+ break;
+ }
+
+ e = parent.cast();
+ }
+
+ return path;
+ }
+
+ /**
+ * Creates a locator String for the given widget. The path can be used to
+ * locate the widget using {@link #getWidgetFromPath(String, Widget)}.
+ * <p/>
+ * Returns null if no path can be determined for the widget or if the widget
+ * is null.
+ *
+ * @param w
+ * The target widget
+ * @return A String locator for the widget
+ */
+ private String getPathForWidget(Widget w) {
+ if (w == null) {
+ return null;
+ }
+ String elementId = w.getElement().getId();
+ if (elementId != null && !elementId.isEmpty()
+ && !elementId.startsWith("gwt-uid-")) {
+ // Use PID_S+id if the user has set an id but do not use it for auto
+ // generated id:s as these might not be consistent
+ return "PID_S" + elementId;
+ } else if (w instanceof VUI) {
+ return "";
+ } else if (w instanceof VWindow) {
+ Connector windowConnector = ConnectorMap.get(client)
+ .getConnector(w);
+ List<WindowConnector> subWindowList = client.getUIConnector()
+ .getSubWindows();
+ int indexOfSubWindow = subWindowList.indexOf(windowConnector);
+ return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
+ } else if (w instanceof RootPanel) {
+ return ROOT_ID;
+ }
+
+ Widget parent = w.getParent();
+
+ String basePath = getPathForWidget(parent);
+ if (basePath == null) {
+ return null;
+ }
+ String simpleName = Util.getSimpleName(w);
+
+ /*
+ * Check if the parent implements Iterable. At least VPopupView does not
+ * implement HasWdgets so we cannot check for that.
+ */
+ if (!(parent instanceof Iterable<?>)) {
+ // Parent does not implement Iterable so we cannot find out which
+ // child this is
+ return null;
+ }
+
+ Iterator<?> i = ((Iterable<?>) parent).iterator();
+ int pos = 0;
+ while (i.hasNext()) {
+ Object child = i.next();
+ if (child == w) {
+ return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
+ + pos + "]";
+ }
+ String simpleName2 = Util.getSimpleName(child);
+ if (simpleName.equals(simpleName2)) {
+ pos++;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Locates the widget based on a String locator.
+ *
+ * @param path
+ * The String locator that identifies the widget.
+ * @param baseWidget
+ * the widget to which the path is relative, null if relative to
+ * root
+ * @return The Widget identified by the String locator or null if the widget
+ * could not be identified.
+ */
+ @SuppressWarnings("unchecked")
+ private Widget getWidgetFromPath(String path, Widget baseWidget) {
+ Widget w = baseWidget;
+ String parts[] = path.split(PARENTCHILD_SEPARATOR);
+
+ for (int i = 0; i < parts.length; i++) {
+ String part = parts[i];
+
+ if (part.equals(ROOT_ID)) {
+ w = RootPanel.get();
+ } else if (part.equals("")) {
+ if (w == null) {
+ w = client.getUIConnector().getWidget();
+ }
+ } else if (w == null) {
+ String id = part;
+ // Must be old static pid (PID_S*)
+ ServerConnector connector = ConnectorMap.get(client)
+ .getConnector(id);
+ if (connector == null) {
+ // Lookup by component id
+ // TODO Optimize this
+ connector = findConnectorById(client.getUIConnector(),
+ id.substring(5));
+ }
+
+ if (connector instanceof ComponentConnector) {
+ w = ((ComponentConnector) connector).getWidget();
+ } else {
+ // Not found
+ return null;
+ }
+ } else if (part.startsWith("domChild[")) {
+ // The target widget has been found and the rest identifies the
+ // element
+ break;
+ } else if (w instanceof Iterable) {
+ // W identifies a widget that contains other widgets, as it
+ // should. Try to locate the child
+ Iterable<?> parent = (Iterable<?>) w;
+
+ // Part is of type "VVerticalLayout[0]", split this into
+ // VVerticalLayout and 0
+ String[] split = part.split("\\[", 2);
+ String widgetClassName = split[0];
+ String indexString = split[1].substring(0,
+ split[1].length() - 1);
+
+ int widgetPosition;
+ try {
+ widgetPosition = Integer.parseInt(indexString);
+ } catch (NumberFormatException e) {
+ // We've probably been fed a new-style Vaadin locator with a
+ // string-form predicate, that doesn't match anything in the
+ // search space.
+ return null;
+ }
+
+ // AbsolutePanel in GridLayout has been removed -> skip it
+ if (w instanceof VGridLayout
+ && "AbsolutePanel".equals(widgetClassName)) {
+ continue;
+ }
+
+ // FlowPane in CSSLayout has been removed -> skip it
+ if (w instanceof VCssLayout
+ && "VCssLayout$FlowPane".equals(widgetClassName)) {
+ continue;
+ }
+
+ // ChildComponentContainer and VOrderedLayout$Slot have been
+ // replaced with Slot
+ if (w instanceof VAbstractOrderedLayout
+ && ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot"
+ .equals(widgetClassName))) {
+ widgetClassName = "Slot";
+ }
+
+ if (w instanceof VTabsheetPanel && widgetPosition != 0) {
+ // TabSheetPanel now only contains 1 connector => the index
+ // is always 0 which indicates the widget in the active tab
+ widgetPosition = 0;
+ }
+ if (w instanceof VOverlay
+ && "VCalendarPanel".equals(widgetClassName)) {
+ // Vaadin 7.1 adds a wrapper for datefield popups
+ parent = (Iterable<?>) ((Iterable<?>) parent).iterator()
+ .next();
+ }
+ /*
+ * The new grid and ordered layouts do not contain
+ * ChildComponentContainer widgets. This is instead simulated by
+ * constructing a path step that would find the desired widget
+ * from the layout and injecting it as the next search step
+ * (which would originally have found the widget inside the
+ * ChildComponentContainer)
+ */
+ if ((w instanceof VGridLayout)
+ && "ChildComponentContainer".equals(widgetClassName)
+ && i + 1 < parts.length) {
+
+ HasWidgets layout = (HasWidgets) w;
+
+ String nextPart = parts[i + 1];
+ String[] nextSplit = nextPart.split("\\[", 2);
+ String nextWidgetClassName = nextSplit[0];
+
+ // Find the n:th child and count the number of children with
+ // the same type before it
+ int nextIndex = 0;
+ for (Widget child : layout) {
+ boolean matchingType = nextWidgetClassName.equals(Util
+ .getSimpleName(child));
+ if (matchingType && widgetPosition == 0) {
+ // This is the n:th child that we looked for
+ break;
+ } else if (widgetPosition < 0) {
+ // Error if we're past the desired position without
+ // a match
+ return null;
+ } else if (matchingType) {
+ // If this was another child of the expected type,
+ // increase the count for the next step
+ nextIndex++;
+ }
+
+ // Don't count captions
+ if (!(child instanceof VCaption)) {
+ widgetPosition--;
+ }
+ }
+
+ // Advance to the next step, this time checking for the
+ // actual child widget
+ parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']';
+ continue;
+ }
+
+ // Locate the child
+ Iterator<? extends Widget> iterator;
+
+ /*
+ * VWindow and VContextMenu workarounds for backwards
+ * compatibility
+ */
+ if (widgetClassName.equals("VWindow")) {
+ List<WindowConnector> windows = client.getUIConnector()
+ .getSubWindows();
+ List<VWindow> windowWidgets = new ArrayList<VWindow>(
+ windows.size());
+ for (WindowConnector wc : windows) {
+ windowWidgets.add(wc.getWidget());
+ }
+ iterator = windowWidgets.iterator();
+ } else if (widgetClassName.equals("VContextMenu")) {
+ return client.getContextMenu();
+ } else {
+ iterator = (Iterator<? extends Widget>) parent.iterator();
+ }
+
+ boolean ok = false;
+
+ // Find the widgetPosition:th child of type "widgetClassName"
+ while (iterator.hasNext()) {
+
+ Widget child = iterator.next();
+ String simpleName2 = Util.getSimpleName(child);
+
+ if (!widgetClassName.equals(simpleName2)
+ && child instanceof Slot) {
+ /*
+ * Support legacy tests without any selector for the
+ * Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by
+ * directly checking the stuff inside the slot
+ */
+ child = ((Slot) child).getWidget();
+ simpleName2 = Util.getSimpleName(child);
+ }
+
+ if (widgetClassName.equals(simpleName2)) {
+ if (widgetPosition == 0) {
+ w = child;
+ ok = true;
+ break;
+ }
+ widgetPosition--;
+
+ }
+ }
+
+ if (!ok) {
+ // Did not find the child
+ return null;
+ }
+ } else {
+ // W identifies something that is not a "HasWidgets". This
+ // should not happen as all widget containers should implement
+ // HasWidgets.
+ return null;
+ }
+ }
+
+ return w;
+ }
+
+ private ServerConnector findConnectorById(ServerConnector root, String id) {
+ SharedState state = root.getState();
+ if (state instanceof AbstractComponentState
+ && id.equals(((AbstractComponentState) state).id)) {
+ return root;
+ }
+ for (ServerConnector child : root.getChildren()) {
+ ServerConnector found = findConnectorById(child, id);
+ if (found != null) {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java
new file mode 100644
index 0000000000..e892f43d76
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+import java.util.List;
+
+import com.google.gwt.user.client.Element;
+
+/**
+ * This interface should be implemented by all locator strategies. A locator
+ * strategy is responsible for generating and decoding a string that identifies
+ * an element in the DOM. A strategy can implement its own syntax for the
+ * locator string, which may be completely different from any other strategy's
+ * syntax.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface LocatorStrategy {
+
+ /**
+ * Test the given input path for formatting errors. If a given path can not
+ * be validated, the locator strategy will not be attempted.
+ *
+ * @param path
+ * a locator path expression
+ * @return true, if the implementing class can process the given path,
+ * otherwise false
+ */
+ boolean validatePath(String path);
+
+ /**
+ * Generates a String locator which uniquely identifies the target element.
+ * The {@link #getElementByPath(String)} method can be used for the inverse
+ * operation, i.e. locating an element based on the return value from this
+ * method.
+ * <p>
+ * Note that getElementByPath(getPathForElement(element)) == element is not
+ * always true as #getPathForElement(Element) can return a path to another
+ * element if the widget determines an action on the other element will give
+ * the same result as the action on the target element.
+ * </p>
+ *
+ * @param targetElement
+ * The element to generate a path for.
+ * @return A String locator that identifies the target element or null if a
+ * String locator could not be created.
+ */
+ String getPathForElement(Element targetElement);
+
+ /**
+ * Locates an element using a String locator (path) which identifies a DOM
+ * element. The {@link #getPathForElement(Element)} method can be used for
+ * the inverse operation, i.e. generating a string expression for a DOM
+ * element.
+ *
+ * @param path
+ * The String locator which identifies the target element.
+ * @return The DOM element identified by {@code path} or null if the element
+ * could not be located.
+ */
+ Element getElementByPath(String path);
+
+ /**
+ * Locates an element using a String locator (path) which identifies a DOM
+ * element. The path starts from the specified root element.
+ *
+ * @see #getElementByPath(String)
+ *
+ * @param path
+ * The String locator which identifies the target element.
+ * @param root
+ * The element that is at the root of the path.
+ * @return The DOM element identified by {@code path} or null if the element
+ * could not be located.
+ */
+ Element getElementByPathStartingAt(String path, Element root);
+
+ /**
+ * Locates all elements that match a String locator (path) which identifies
+ * DOM elements.
+ *
+ * This functionality is limited in {@link LegacyLocatorStrategy}.
+ *
+ * @param path
+ * The String locator which identifies target elements.
+ * @return List that contains all matched elements. Empty list if none
+ * found.
+ */
+ List<Element> getElementsByPath(String path);
+
+ /**
+ * Locates all elements that match a String locator (path) which identifies
+ * DOM elements. The path starts from the specified root element.
+ *
+ * This functionality is limited in {@link LegacyLocatorStrategy}.
+ *
+ * @see #getElementsByPath(String)
+ *
+ * @param path
+ * The String locator which identifies target elements.
+ * @param root
+ * The element that is at the root of the path.
+ * @return List that contains all matched elements. Empty list if none
+ * found.
+ */
+
+ List<Element> getElementsByPathStartingAt(String path, Element root);
+}
diff --git a/client/src/com/vaadin/client/componentlocator/LocatorUtil.java b/client/src/com/vaadin/client/componentlocator/LocatorUtil.java
new file mode 100644
index 0000000000..04624920a9
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/LocatorUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+/**
+ * Common String manipulator utilities used in VaadinFinderLocatorStrategy and
+ * SelectorPredicates.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class LocatorUtil {
+
+ /**
+ * Find first occurrence of character that's not inside quotes starting from
+ * specified index.
+ *
+ * @param str
+ * Full string for searching
+ * @param find
+ * Character we want to find
+ * @param startingAt
+ * Index where we start
+ * @return Index of character. -1 if character not found
+ */
+ protected static int indexOfIgnoringQuoted(String str, char find,
+ int startingAt) {
+ boolean quote = false;
+ String quoteChars = "'\"";
+ char currentQuote = '"';
+ for (int i = startingAt; i < str.length(); ++i) {
+ char cur = str.charAt(i);
+ if (quote) {
+ if (cur == currentQuote) {
+ quote = !quote;
+ }
+ continue;
+ } else if (cur == find) {
+ return i;
+ } else {
+ if (quoteChars.indexOf(cur) >= 0) {
+ currentQuote = cur;
+ quote = !quote;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find first occurrence of character that's not inside quotes starting from
+ * the beginning of string.
+ *
+ * @param str
+ * Full string for searching
+ * @param find
+ * Character we want to find
+ * @return Index of character. -1 if character not found
+ */
+ protected static int indexOfIgnoringQuoted(String str, char find) {
+ return indexOfIgnoringQuoted(str, find, 0);
+ }
+}
diff --git a/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java
new file mode 100644
index 0000000000..32b33005ed
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SelectorPredicates are statements about the state of different components
+ * that VaadinFinderLocatorStrategy is finding. SelectorPredicates also provide
+ * useful information of said components to debug window by giving means to
+ * provide better variable naming.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class SelectorPredicate {
+ private String name = "";
+ private String value = "";
+ private boolean wildcard = false;
+ private int index = -1;
+
+ public static List<SelectorPredicate> extractPostFilterPredicates(
+ String path) {
+ if (path.startsWith("(")) {
+ return extractPredicates(path.substring(path.lastIndexOf(')')));
+ }
+ return new ArrayList<SelectorPredicate>();
+ }
+
+ /**
+ * Generate a list of predicates from a single predicate string
+ *
+ * @param str
+ * a comma separated string of predicates
+ * @return a List of Predicate objects
+ */
+ public static List<SelectorPredicate> extractPredicates(String path) {
+ List<SelectorPredicate> predicates = new ArrayList<SelectorPredicate>();
+
+ String predicateStr = extractPredicateString(path);
+ if (null == predicateStr || predicateStr.length() == 0) {
+ return predicates;
+ }
+
+ // Extract input strings
+ List<String> input = readPredicatesFromString(predicateStr);
+
+ // Process each predicate into proper predicate descriptor
+ for (String s : input) {
+ SelectorPredicate p = new SelectorPredicate();
+ s = s.trim();
+
+ try {
+ // If we can parse out the predicate as a pure index argument,
+ // stop processing here.
+ p.index = Integer.parseInt(s);
+ predicates.add(p);
+
+ continue;
+ } catch (Exception e) {
+ p.index = -1;
+ }
+
+ int idx = LocatorUtil.indexOfIgnoringQuoted(s, '=');
+ if (idx < 0) {
+ continue;
+ }
+ p.name = s.substring(0, idx);
+ p.value = s.substring(idx + 1);
+
+ if (p.value.equals("?")) {
+ p.wildcard = true;
+ p.value = null;
+ } else {
+ // Only unquote predicate value once we're sure it's a proper
+ // value...
+
+ p.value = unquote(p.value);
+ }
+
+ predicates.add(p);
+ }
+ // Move any (and all) index predicates to last place in the list.
+ for (int i = 0, l = predicates.size(); i < l - 1; ++i) {
+ if (predicates.get(i).index > -1) {
+ predicates.add(predicates.remove(i));
+ --i;
+ --l;
+ }
+ }
+
+ return predicates;
+ }
+
+ /**
+ * Splits the predicate string to list of predicate strings.
+ *
+ * @param predicateStr
+ * Comma separated predicate strings
+ * @return List of predicate strings
+ */
+ private static List<String> readPredicatesFromString(String predicateStr) {
+ List<String> predicates = new ArrayList<String>();
+ int prevIdx = 0;
+ int idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx);
+
+ while (idx > -1) {
+ predicates.add(predicateStr.substring(prevIdx, idx));
+ prevIdx = idx + 1;
+ idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx);
+ }
+ predicates.add(predicateStr.substring(prevIdx));
+
+ return predicates;
+ }
+
+ /**
+ * Returns the predicate string, i.e. the string between the brackets in a
+ * path fragment. Examples: <code>
+ * VTextField[0] => 0
+ * VTextField[caption='foo'] => caption='foo'
+ * </code>
+ *
+ * @param pathFragment
+ * The path fragment from which to extract the predicate string.
+ * @return The predicate string for the path fragment or empty string if not
+ * found.
+ */
+ private static String extractPredicateString(String pathFragment) {
+ int ixOpenBracket = LocatorUtil
+ .indexOfIgnoringQuoted(pathFragment, '[');
+ if (ixOpenBracket >= 0) {
+ int ixCloseBracket = LocatorUtil.indexOfIgnoringQuoted(
+ pathFragment, ']', ixOpenBracket);
+ return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket);
+ }
+ return "";
+ }
+
+ /**
+ * Removes the surrounding quotes from a string if it is quoted.
+ *
+ * @param str
+ * the possibly quoted string
+ * @return an unquoted version of str
+ */
+ private static String unquote(String str) {
+ if ((str.startsWith("\"") && str.endsWith("\""))
+ || (str.startsWith("'") && str.endsWith("'"))) {
+ return str.substring(1, str.length() - 1);
+ }
+ return str;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name
+ * the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @param value
+ * the value to set
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the index
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * @param index
+ * the index to set
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /**
+ * @return the wildcard
+ */
+ public boolean isWildcard() {
+ return wildcard;
+ }
+
+ /**
+ * @param wildcard
+ * the wildcard to set
+ */
+ public void setWildcard(boolean wildcard) {
+ this.wildcard = wildcard;
+ }
+}
diff --git a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java
new file mode 100644
index 0000000000..49090b66db
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java
@@ -0,0 +1,748 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.componentlocator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.HasComponentsConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.metadata.TypeDataStore;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.client.ui.SubPartAware;
+import com.vaadin.client.ui.VNotification;
+
+/**
+ * The VaadinFinder locator strategy implements an XPath-like syntax for
+ * locating elements in Vaadin applications. This is used in the new
+ * VaadinFinder API in TestBench 4.
+ *
+ * Examples of the supported syntax:
+ * <ul>
+ * <li>Find the third text field in the DOM: {@code //VTextField[2]}</li>
+ * <li>Find the second button inside the first vertical layout:
+ * {@code //VVerticalLayout/VButton[1]}</li>
+ * <li>Find the first column on the third row of the "Accounts" table:
+ * {@code //VScrollTable[caption="Accounts"]#row[2]/col[0]}</li>
+ * </ul>
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class VaadinFinderLocatorStrategy implements LocatorStrategy {
+
+ public static final String SUBPART_SEPARATOR = "#";
+
+ private final ApplicationConnection client;
+
+ /**
+ * Internal descriptor for connector/element/widget name combinations
+ */
+ private static final class ConnectorPath {
+ private String name;
+ private ComponentConnector connector;
+ }
+
+ public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) {
+ client = clientConnection;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPathForElement(Element targetElement) {
+ if (targetElement == null) {
+ return "";
+ }
+
+ List<ConnectorPath> hierarchy = getConnectorHierarchyForElement(targetElement);
+ List<String> path = new ArrayList<String>();
+
+ // Assemble longname path components back-to-forth with useful
+ // predicates - first try ID, then caption.
+ for (int i = 0; i < hierarchy.size(); ++i) {
+ ConnectorPath cp = hierarchy.get(i);
+ String pathFragment = cp.name;
+ String identifier = getPropertyValue(cp.connector, "id");
+
+ if (identifier != null) {
+ pathFragment += "[id=\"" + identifier + "\"]";
+ } else {
+ identifier = getPropertyValue(cp.connector, "caption");
+ if (identifier != null) {
+ pathFragment += "[caption=\"" + identifier + "\"]";
+ }
+ }
+ path.add(pathFragment);
+ }
+
+ if (path.size() == 0) {
+ // If we didn't find a single element, return null..
+ return null;
+ }
+
+ return getBestSelector(generateQueries(path), targetElement);
+ }
+
+ /**
+ * Search different queries for the best one. Use the fact that the lowest
+ * possible index is with the last selector. Last selector is the full
+ * search path containing the complete Component hierarchy.
+ *
+ * @param selectors
+ * List of selectors
+ * @param target
+ * Target element
+ * @return Best selector string formatted with a post filter
+ */
+ private String getBestSelector(List<String> selectors, Element target) {
+ // The last selector gives us smallest list index for target element.
+ String bestSelector = selectors.get(selectors.size() - 1);
+ int min = getElementsByPath(bestSelector).indexOf(target);
+ if (selectors.size() > 1
+ && min == getElementsByPath(selectors.get(0)).indexOf(target)) {
+ // The first selector has same index as last. It's much shorter.
+ bestSelector = selectors.get(0);
+ } else if (selectors.size() > 2) {
+ // See if we get minimum from second last. If not then we already
+ // have the best one.. Second last one contains almost full
+ // component hierarchy.
+ if (getElementsByPath(selectors.get(selectors.size() - 2)).indexOf(
+ target) == min) {
+ for (int i = 1; i < selectors.size() - 2; ++i) {
+ // Loop through the remaining selectors and look for one
+ // with the same index
+ if (getElementsByPath(selectors.get(i)).indexOf(target) == min) {
+ bestSelector = selectors.get(i);
+ break;
+ }
+ }
+
+ }
+ }
+ return "(" + bestSelector + ")[" + min + "]";
+
+ }
+
+ /**
+ * Function to generate all possible search paths for given component list.
+ * Function strips out all the com.vaadin.ui. prefixes from elements as this
+ * functionality makes generating a query later on easier.
+ *
+ * @param components
+ * List of components
+ * @return List of Vaadin selectors
+ */
+ private List<String> generateQueries(List<String> components) {
+ // Prepare to loop through all the elements.
+ List<String> paths = new ArrayList<String>();
+ int compIdx = 0;
+ String basePath = components.get(compIdx).replace("com.vaadin.ui.", "");
+ // Add a basic search for the first element (eg. //Button)
+ paths.add((components.size() == 1 ? "/" : "//") + basePath);
+ while (++compIdx < components.size()) {
+ // Loop through the remaining components
+ for (int i = components.size() - 1; i >= compIdx; --i) {
+ boolean recursive = false;
+ if (i > compIdx) {
+ recursive = true;
+ }
+ paths.add((i == components.size() - 1 ? "/" : "//")
+ + components.get(i).replace("com.vaadin.ui.", "")
+ + (recursive ? "//" : "/") + basePath);
+ }
+ // Add the element at index compIdx to the basePath so it is
+ // included in all the following searches.
+ basePath = components.get(compIdx).replace("com.vaadin.ui.", "")
+ + "/" + basePath;
+ }
+
+ return paths;
+ }
+
+ /**
+ * Helper method to get the string-form value of a named property of a
+ * component connector
+ *
+ * @since 7.2
+ * @param c
+ * any ComponentConnector instance
+ * @param propertyName
+ * property name to test for
+ * @return a string, if the property is found, or null, if the property does
+ * not exist on the object (or some other error is encountered).
+ */
+ private String getPropertyValue(ComponentConnector c, String propertyName) {
+ Property prop = AbstractConnector.getStateType(c).getProperty(
+ propertyName);
+ try {
+ return prop.getValue(c.getState()).toString();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Generate a list representing the top-to-bottom connector hierarchy for
+ * any given element. ConnectorPath element provides long- and short names,
+ * as well as connector and widget root element references.
+ *
+ * @since 7.2
+ * @param elem
+ * any Element that is part of a widget hierarchy
+ * @return a list of ConnectorPath objects, in descending order towards the
+ * common root container.
+ */
+ private List<ConnectorPath> getConnectorHierarchyForElement(Element elem) {
+ Element e = elem;
+ ComponentConnector c = Util.findPaintable(client, e);
+ List<ConnectorPath> connectorHierarchy = new ArrayList<ConnectorPath>();
+
+ while (c != null) {
+
+ for (String id : getIDsForConnector(c)) {
+ ConnectorPath cp = new ConnectorPath();
+ cp.name = getFullClassName(id);
+ cp.connector = c;
+
+ // We want to make an exception for the UI object, since it's
+ // our default search context (and can't be found inside itself)
+ if (!cp.name.equals("com.vaadin.ui.UI")) {
+ connectorHierarchy.add(cp);
+ }
+ }
+
+ e = (Element) e.getParentElement();
+ if (e != null) {
+ c = Util.findPaintable(client, e);
+ e = c != null ? c.getWidget().getElement() : null;
+ }
+
+ }
+
+ return connectorHierarchy;
+ }
+
+ private boolean isNotificationExpression(String path) {
+ String[] starts = { "//", "/" };
+
+ String[] frags = { "com.vaadin.ui.Notification.class",
+ "com.vaadin.ui.Notification", "VNotification.class",
+ "VNotification", "Notification.class", "Notification" };
+
+ String[] ends = { "/", "[" };
+
+ for (String s : starts) {
+ for (String f : frags) {
+ if (path.equals(s + f)) {
+ return true;
+ }
+
+ for (String e : ends) {
+ if (path.startsWith(s + f + e)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Element> getElementsByPath(String path) {
+ List<SelectorPredicate> postFilters = SelectorPredicate
+ .extractPostFilterPredicates(path);
+ if (postFilters.size() > 0) {
+ path = path.substring(1, path.lastIndexOf(')'));
+ }
+
+ List<Element> elements = new ArrayList<Element>();
+ if (isNotificationExpression(path)) {
+
+ for (VNotification n : findNotificationsByPath(path)) {
+ elements.add(n.getElement());
+ }
+
+ } else {
+
+ elements.addAll(eliminateDuplicates(getElementsByPathStartingAtConnector(
+ path, client.getUIConnector())));
+ }
+
+ for (SelectorPredicate p : postFilters) {
+ // Post filtering supports only indexes and follows instruction
+ // blindly. Index that is outside of our list results into an empty
+ // list and multiple indexes are likely to ruin a search completely
+ if (p.getIndex() >= 0) {
+ if (p.getIndex() >= elements.size()) {
+ elements.clear();
+ } else {
+ Element e = elements.get(p.getIndex());
+ elements.clear();
+ elements.add(e);
+ }
+ }
+ }
+
+ return elements;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPath(String path) {
+ List<Element> elements = getElementsByPath(path);
+ if (elements.isEmpty()) {
+ return null;
+ }
+ return elements.get(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPathStartingAt(String path, Element root) {
+ List<Element> elements = getElementsByPathStartingAt(path, root);
+ if (elements.isEmpty()) {
+ return null;
+ }
+ return elements.get(0);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<Element> getElementsByPathStartingAt(String path, Element root) {
+ List<SelectorPredicate> postFilters = SelectorPredicate
+ .extractPostFilterPredicates(path);
+ if (postFilters.size() > 0) {
+ path = path.substring(1, path.lastIndexOf(')'));
+ }
+
+ List<Element> elements = getElementsByPathStartingAtConnector(path,
+ Util.findPaintable(client, root));
+
+ for (SelectorPredicate p : postFilters) {
+ // Post filtering supports only indexes and follows instruction
+ // blindly. Index that is outside of our list results into an empty
+ // list and multiple indexes are likely to ruin a search completely
+ if (p.getIndex() >= 0) {
+ if (p.getIndex() >= elements.size()) {
+ elements.clear();
+ } else {
+ Element e = elements.get(p.getIndex());
+ elements.clear();
+ elements.add(e);
+ }
+ }
+ }
+
+ return elements;
+ }
+
+ /**
+ * Special case for finding notifications as they have no connectors and are
+ * directly attached to {@link RootPanel}.
+ *
+ * @param path
+ * The path of the notification, should be
+ * {@code "//VNotification"} optionally followed by an index in
+ * brackets.
+ * @return the notification element or null if not found.
+ */
+ private List<VNotification> findNotificationsByPath(String path) {
+
+ List<VNotification> notifications = new ArrayList<VNotification>();
+ for (Widget w : RootPanel.get()) {
+ if (w instanceof VNotification) {
+ notifications.add((VNotification) w);
+ }
+ }
+
+ List<SelectorPredicate> predicates = SelectorPredicate
+ .extractPredicates(path);
+ for (SelectorPredicate p : predicates) {
+
+ if (p.getIndex() > -1) {
+ VNotification n = notifications.get(p.getIndex());
+ notifications.clear();
+ if (n != null) {
+ notifications.add(n);
+ }
+ }
+
+ }
+
+ return eliminateDuplicates(notifications);
+ }
+
+ /**
+ * Finds a list of elements by the specified path, starting traversal of the
+ * connector hierarchy from the specified root.
+ *
+ * @param path
+ * the locator path
+ * @param root
+ * the root connector
+ * @return the list of elements identified by path or empty list if not
+ * found.
+ */
+ private List<Element> getElementsByPathStartingAtConnector(String path,
+ ComponentConnector root) {
+ String[] pathComponents = path.split(SUBPART_SEPARATOR);
+ List<ComponentConnector> connectors;
+ if (pathComponents[0].length() > 0) {
+ connectors = findConnectorsByPath(pathComponents[0],
+ Arrays.asList(root));
+ } else {
+ connectors = Arrays.asList(root);
+ }
+
+ List<Element> output = new ArrayList<Element>();
+ if (null != connectors && !connectors.isEmpty()) {
+ if (pathComponents.length > 1) {
+ // We have subparts
+ for (ComponentConnector connector : connectors) {
+ if (connector.getWidget() instanceof SubPartAware) {
+ output.add(((SubPartAware) connector.getWidget())
+ .getSubPartElement(pathComponents[1]));
+ }
+ }
+ } else {
+ for (ComponentConnector connector : connectors) {
+ output.add(connector.getWidget().getElement());
+ }
+ }
+ }
+ return eliminateDuplicates(output);
+ }
+
+ /**
+ * Recursively finds connectors for the elements identified by the provided
+ * path by traversing the connector hierarchy starting from {@code parents}
+ * connectors.
+ *
+ * @param path
+ * The path identifying elements.
+ * @param parents
+ * The list of connectors to start traversing from.
+ * @return The list of connectors identified by {@code path} or empty list
+ * if no such connectors could be found.
+ */
+ private List<ComponentConnector> findConnectorsByPath(String path,
+ List<ComponentConnector> parents) {
+ boolean findRecursively = path.startsWith("//");
+ // Strip away the one or two slashes from the beginning of the path
+ path = path.substring(findRecursively ? 2 : 1);
+
+ String[] fragments = splitFirstFragmentFromTheRest(path);
+
+ List<ComponentConnector> connectors = new ArrayList<ComponentConnector>();
+ for (ComponentConnector parent : parents) {
+ connectors.addAll(filterMatches(
+ collectPotentialMatches(parent, fragments[0],
+ findRecursively), SelectorPredicate
+ .extractPredicates(fragments[0])));
+ }
+
+ if (!connectors.isEmpty() && fragments.length > 1) {
+ return (findConnectorsByPath(fragments[1], connectors));
+ }
+ return eliminateDuplicates(connectors);
+ }
+
+ /**
+ * Go through a list of potentially matching components, modifying that list
+ * until all elements that remain in that list match the complete list of
+ * predicates.
+ *
+ * @param potentialMatches
+ * a list of component connectors. Will be changed.
+ * @param predicates
+ * an immutable list of predicates
+ * @return filtered list of component connectors.
+ */
+ private List<ComponentConnector> filterMatches(
+ List<ComponentConnector> potentialMatches,
+ List<SelectorPredicate> predicates) {
+
+ for (SelectorPredicate p : predicates) {
+
+ if (p.getIndex() > -1) {
+ try {
+ ComponentConnector v = potentialMatches.get(p.getIndex());
+ potentialMatches.clear();
+ potentialMatches.add(v);
+ } catch (IndexOutOfBoundsException e) {
+ potentialMatches.clear();
+ }
+
+ continue;
+ }
+
+ for (int i = 0, l = potentialMatches.size(); i < l; ++i) {
+
+ String propData = getPropertyValue(potentialMatches.get(i),
+ p.getName());
+
+ if ((p.isWildcard() && propData == null)
+ || (!p.isWildcard() && !p.getValue().equals(propData))) {
+ potentialMatches.remove(i);
+ --l;
+ --i;
+ }
+ }
+
+ }
+
+ return eliminateDuplicates(potentialMatches);
+ }
+
+ /**
+ * Collects all connectors that match the widget class name of the path
+ * fragment. If the {@code collectRecursively} parameter is true, a
+ * depth-first search of the connector hierarchy is performed.
+ *
+ * Searching depth-first ensure that we can return the matches in correct
+ * order for selecting based on index predicates.
+ *
+ * @param parent
+ * The {@link ComponentConnector} to start the search from.
+ * @param pathFragment
+ * The path fragment identifying which type of widget to search
+ * for.
+ * @param collectRecursively
+ * If true, all matches from all levels below {@code parent} will
+ * be collected. If false only direct children will be collected.
+ * @return A list of {@link ComponentConnector}s matching the widget type
+ * specified in the {@code pathFragment}.
+ */
+ private List<ComponentConnector> collectPotentialMatches(
+ ComponentConnector parent, String pathFragment,
+ boolean collectRecursively) {
+ ArrayList<ComponentConnector> potentialMatches = new ArrayList<ComponentConnector>();
+ if (parent instanceof HasComponentsConnector) {
+ List<ComponentConnector> children = ((HasComponentsConnector) parent)
+ .getChildComponents();
+ for (ComponentConnector child : children) {
+ String widgetName = getWidgetName(pathFragment);
+ if (connectorMatchesPathFragment(child, widgetName)) {
+ potentialMatches.add(child);
+ }
+ if (collectRecursively) {
+ potentialMatches.addAll(collectPotentialMatches(child,
+ pathFragment, collectRecursively));
+ }
+ }
+ }
+ return eliminateDuplicates(potentialMatches);
+ }
+
+ private List<String> getIDsForConnector(ComponentConnector connector) {
+ Class<?> connectorClass = connector.getClass();
+ List<String> ids = new ArrayList<String>();
+
+ TypeDataStore.get().findIdentifiersFor(connectorClass).addAllTo(ids);
+
+ return ids;
+ }
+
+ /**
+ * Determines whether a connector matches a path fragment. This is done by
+ * comparing the path fragment to the name of the widget type of the
+ * connector.
+ *
+ * @param connector
+ * The connector to compare.
+ * @param widgetName
+ * The name of the widget class.
+ * @return true if the widget type of the connector equals the widget type
+ * identified by the path fragment.
+ */
+ private boolean connectorMatchesPathFragment(ComponentConnector connector,
+ String widgetName) {
+
+ List<String> ids = getIDsForConnector(connector);
+
+ Integer[] widgetTags = client.getConfiguration()
+ .getTagsForServerSideClassName(getFullClassName(widgetName));
+ if (widgetTags.length == 0) {
+ widgetTags = client.getConfiguration()
+ .getTagsForServerSideClassName(
+ getFullClassName("com.vaadin.ui." + widgetName));
+ }
+
+ for (int i = 0, l = ids.size(); i < l; ++i) {
+
+ // Fuzz the connector name, so that the client can provide (for
+ // example: /Button, /Button.class, /com.vaadin.ui.Button,
+ // /com.vaadin.ui.Button.class, etc)
+
+ String name = ids.get(i);
+ final String simpleName = getSimpleClassName(name);
+ final String fullName = getFullClassName(name);
+
+ if (widgetTags.length > 0) {
+ Integer[] foundTags = client.getConfiguration()
+ .getTagsForServerSideClassName(fullName);
+ for (int tag : foundTags) {
+ if (tagsMatch(widgetTags, tag)) {
+ return true;
+ }
+ }
+ }
+
+ // Fallback if something failed before.
+ if (widgetName.equals(fullName + ".class")
+ || widgetName.equals(fullName)
+ || widgetName.equals(simpleName + ".class")
+ || widgetName.equals(simpleName) || widgetName.equals(name)) {
+ return true;
+ }
+ }
+
+ // If the server-side class name didn't match, fall back to testing for
+ // the explicit widget name
+ String widget = Util.getSimpleName(connector.getWidget());
+ return widgetName.equals(widget)
+ || widgetName.equals(widget + ".class");
+
+ }
+
+ /**
+ * Extracts the name of the widget class from a path fragment
+ *
+ * @param pathFragment
+ * the path fragment
+ * @return the name of the widget class.
+ */
+ private String getWidgetName(String pathFragment) {
+ String widgetName = pathFragment;
+ int ixBracket = pathFragment.indexOf('[');
+ if (ixBracket >= 0) {
+ widgetName = pathFragment.substring(0, ixBracket);
+ }
+ return widgetName;
+ }
+
+ /**
+ * Splits off the first path fragment from a path and returns an array of
+ * two elements, where the first element is the first path fragment and the
+ * second element is the rest of the path (all remaining path fragments
+ * untouched).
+ *
+ * @param path
+ * The path to split.
+ * @return An array of two elements: The first path fragment and the rest of
+ * the path.
+ */
+ private String[] splitFirstFragmentFromTheRest(String path) {
+ int ixOfSlash = LocatorUtil.indexOfIgnoringQuoted(path, '/');
+ if (ixOfSlash > 0) {
+ return new String[] { path.substring(0, ixOfSlash),
+ path.substring(ixOfSlash) };
+ }
+ return new String[] { path };
+ }
+
+ private String getSimpleClassName(String s) {
+ String[] parts = s.split("\\.");
+ if (s.endsWith(".class")) {
+ return parts[parts.length - 2];
+ }
+ return parts.length > 0 ? parts[parts.length - 1] : s;
+ }
+
+ private String getFullClassName(String s) {
+ if (s.endsWith(".class")) {
+ return s.substring(0, s.lastIndexOf(".class"));
+ }
+ return s;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.client.componentlocator.LocatorStrategy#validatePath(java.
+ * lang.String)
+ */
+ @Override
+ public boolean validatePath(String path) {
+ // This syntax is so difficult to regexp properly, that we'll just try
+ // to find something with it regardless of the correctness of the
+ // syntax...
+ return true;
+ }
+
+ /**
+ * Go through a list, removing all duplicate elements from it. This method
+ * is used to avoid accumulation of duplicate entries in result lists
+ * resulting from low-context recursion.
+ *
+ * Preserves first entry in list, removes others. Preserves list order.
+ *
+ * @return list passed as parameter, after modification
+ */
+ private final <T> List<T> eliminateDuplicates(List<T> list) {
+
+ int l = list.size();
+ for (int j = 0; j < l; ++j) {
+ T ref = list.get(j);
+
+ for (int i = j + 1; i < l; ++i) {
+ if (list.get(i) == ref) {
+ list.remove(i);
+ --i;
+ --l;
+ }
+ }
+ }
+
+ return list;
+ }
+
+ private boolean tagsMatch(Integer[] targets, Integer tag) {
+ for (int i = 0; i < targets.length; ++i) {
+ if (targets[i].equals(tag)) {
+ return true;
+ }
+ }
+
+ try {
+ return tagsMatch(targets,
+ client.getConfiguration().getParentTag(tag));
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java b/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java
new file mode 100644
index 0000000000..7561bc2c03
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.TextDecoration;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ComputedStyle;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.SimpleTree;
+import com.vaadin.client.Util;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Analyze layouts view panel of the debug window.
+ *
+ * @since 7.1.4
+ */
+public class AnalyzeLayoutsPanel extends FlowPanel {
+
+ private List<SelectConnectorListener> listeners = new ArrayList<SelectConnectorListener>();
+
+ public void update() {
+ clear();
+ add(new Label("Analyzing layouts..."));
+ List<ApplicationConnection> runningApplications = ApplicationConfiguration
+ .getRunningApplications();
+ for (ApplicationConnection applicationConnection : runningApplications) {
+ applicationConnection.analyzeLayouts();
+ }
+ }
+
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ clear();
+ JsArray<ValueMap> valueMapArray = meta
+ .getJSValueMapArray("invalidLayouts");
+ int size = valueMapArray.length();
+
+ if (size > 0) {
+ SimpleTree root = new SimpleTree("Layouts analyzed, " + size
+ + " top level problems");
+ for (int i = 0; i < size; i++) {
+ printLayoutError(ac, valueMapArray.get(i), root);
+ }
+ root.open(false);
+ add(root);
+ } else {
+ add(new Label("Layouts analyzed, no top level problems"));
+ }
+
+ Set<ComponentConnector> zeroHeightComponents = new HashSet<ComponentConnector>();
+ Set<ComponentConnector> zeroWidthComponents = new HashSet<ComponentConnector>();
+ findZeroSizeComponents(zeroHeightComponents, zeroWidthComponents,
+ ac.getUIConnector());
+ if (zeroHeightComponents.size() > 0 || zeroWidthComponents.size() > 0) {
+ add(new HTML("<h4> Client side notifications</h4>"
+ + " <em>The following relative sized components were "
+ + "rendered to a zero size container on the client side."
+ + " Note that these are not necessarily invalid "
+ + "states, but reported here as they might be.</em>"));
+ if (zeroHeightComponents.size() > 0) {
+ add(new HTML("<p><strong>Vertically zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroHeightComponents, ac);
+ }
+ if (zeroWidthComponents.size() > 0) {
+ add(new HTML("<p><strong>Horizontally zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroWidthComponents, ac);
+ }
+ }
+
+ }
+
+ private void printClientSideDetectedIssues(
+ Set<ComponentConnector> zeroSized, ApplicationConnection ac) {
+
+ // keep track of already highlighted parents
+ HashSet<String> parents = new HashSet<String>();
+
+ for (final ComponentConnector connector : zeroSized) {
+ final ServerConnector parent = connector.getParent();
+ final String parentId = parent.getConnectorId();
+
+ final Label errorDetails = new Label(Util.getSimpleName(connector)
+ + "[" + connector.getConnectorId() + "]" + " inside "
+ + Util.getSimpleName(parent));
+
+ if (parent instanceof ComponentConnector) {
+ final ComponentConnector parentConnector = (ComponentConnector) parent;
+ if (!parents.contains(parentId)) {
+ parents.add(parentId);
+ Highlight.show(parentConnector, "yellow");
+ }
+
+ errorDetails.addMouseOverHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.hideAll();
+ Highlight.show(parentConnector, "yellow");
+ Highlight.show(connector);
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ });
+ errorDetails.addMouseOutHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ });
+ errorDetails.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ fireSelectEvent(connector);
+ }
+ });
+
+ }
+
+ Highlight.show(connector);
+ add(errorDetails);
+
+ }
+ }
+
+ private void printLayoutError(ApplicationConnection ac, ValueMap valueMap,
+ SimpleTree root) {
+ final String pid = valueMap.getString("id");
+
+ // find connector
+ final ComponentConnector connector = (ComponentConnector) ConnectorMap
+ .get(ac).getConnector(pid);
+
+ if (connector == null) {
+ root.add(new SimpleTree("[" + pid + "] NOT FOUND"));
+ return;
+ }
+
+ Highlight.show(connector);
+
+ final SimpleTree errorNode = new SimpleTree(
+ Util.getSimpleName(connector) + " id: " + pid);
+ errorNode.addDomHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.showOnly(connector);
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ }, MouseOverEvent.getType());
+ errorNode.addDomHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ }, MouseOutEvent.getType());
+
+ errorNode.addDomHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getNativeEvent().getEventTarget().cast() == errorNode
+ .getElement().getChild(1).cast()) {
+ fireSelectEvent(connector);
+ }
+ }
+ }, ClickEvent.getType());
+
+ VerticalPanel errorDetails = new VerticalPanel();
+
+ if (valueMap.containsKey("heightMsg")) {
+ errorDetails.add(new Label("Height problem: "
+ + valueMap.getString("heightMsg")));
+ }
+ if (valueMap.containsKey("widthMsg")) {
+ errorDetails.add(new Label("Width problem: "
+ + valueMap.getString("widthMsg")));
+ }
+ if (errorDetails.getWidgetCount() > 0) {
+ errorNode.add(errorDetails);
+ }
+ if (valueMap.containsKey("subErrors")) {
+ HTML l = new HTML(
+ "<em>Expand this node to show problems that may be dependent on this problem.</em>");
+ errorDetails.add(l);
+ JsArray<ValueMap> suberrors = valueMap
+ .getJSValueMapArray("subErrors");
+ for (int i = 0; i < suberrors.length(); i++) {
+ ValueMap value = suberrors.get(i);
+ printLayoutError(ac, value, errorNode);
+ }
+
+ }
+ root.add(errorNode);
+ }
+
+ private void findZeroSizeComponents(
+ Set<ComponentConnector> zeroHeightComponents,
+ Set<ComponentConnector> zeroWidthComponents,
+ ComponentConnector connector) {
+ Widget widget = connector.getWidget();
+ ComputedStyle computedStyle = new ComputedStyle(widget.getElement());
+ if (computedStyle.getIntProperty("height") == 0) {
+ zeroHeightComponents.add(connector);
+ }
+ if (computedStyle.getIntProperty("width") == 0) {
+ zeroWidthComponents.add(connector);
+ }
+ List<ServerConnector> children = connector.getChildren();
+ for (ServerConnector serverConnector : children) {
+ if (serverConnector instanceof ComponentConnector) {
+ findZeroSizeComponents(zeroHeightComponents,
+ zeroWidthComponents,
+ (ComponentConnector) serverConnector);
+ }
+ }
+ }
+
+ public void addListener(SelectConnectorListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(SelectConnectorListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void fireSelectEvent(ServerConnector connector) {
+ for (SelectConnectorListener listener : listeners) {
+ listener.select(connector, null);
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java b/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java
new file mode 100644
index 0000000000..fc7b55497e
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.JsArrayObject;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * Connector information view panel of the debug window.
+ *
+ * @since 7.1.4
+ */
+public class ConnectorInfoPanel extends FlowPanel {
+
+ /**
+ * Update the panel to show information about a connector.
+ *
+ * @param connector
+ */
+ public void update(ServerConnector connector) {
+ SharedState state = connector.getState();
+
+ Set<String> ignoreProperties = new HashSet<String>();
+ ignoreProperties.add("id");
+
+ String html = getRowHTML("Id", connector.getConnectorId());
+ html += getRowHTML("Connector", Util.getSimpleName(connector));
+
+ if (connector instanceof ComponentConnector) {
+ ComponentConnector component = (ComponentConnector) connector;
+
+ ignoreProperties.addAll(Arrays.asList("caption", "description",
+ "width", "height"));
+
+ AbstractComponentState componentState = component.getState();
+
+ html += getRowHTML("Widget",
+ Util.getSimpleName(component.getWidget()));
+ html += getRowHTML("Caption", componentState.caption);
+ html += getRowHTML("Description", componentState.description);
+ html += getRowHTML("Width", componentState.width + " (actual: "
+ + component.getWidget().getOffsetWidth() + "px)");
+ html += getRowHTML("Height", componentState.height + " (actual: "
+ + component.getWidget().getOffsetHeight() + "px)");
+ }
+
+ try {
+ JsArrayObject<Property> properties = AbstractConnector
+ .getStateType(connector).getPropertiesAsArray();
+ for (int i = 0; i < properties.size(); i++) {
+ Property property = properties.get(i);
+ String name = property.getName();
+ if (!ignoreProperties.contains(name)) {
+ html += getRowHTML(property.getDisplayName(),
+ property.getValue(state));
+ }
+ }
+ } catch (NoDataException e) {
+ html += "<div>Could not read state, error has been logged to the console</div>";
+ VConsole.error(e);
+ }
+
+ clear();
+ add(new HTML(html));
+ }
+
+ private String getRowHTML(String caption, Object value) {
+ return "<div class=\"" + VDebugWindow.STYLENAME
+ + "-row\"><span class=\"caption\">" + caption
+ + "</span><span class=\"value\">"
+ + Util.escapeHTML(String.valueOf(value)) + "</span></div>";
+ }
+
+ /**
+ * Clear the contents of the panel.
+ */
+ public void clearContents() {
+ clear();
+ }
+}
diff --git a/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java b/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java
new file mode 100644
index 0000000000..755f076b7a
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.FastStringSet;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.SimpleTree;
+import com.vaadin.client.Util;
+
+/**
+ * Hierarchy view panel of the debug window. This class can be used in various
+ * debug window sections to show the current connector hierarchy.
+ *
+ * @since 7.1.4
+ */
+public class HierarchyPanel extends FlowPanel {
+
+ // TODO separate click listeners for simple selection and doubleclick
+ private List<SelectConnectorListener> listeners = new ArrayList<SelectConnectorListener>();
+
+ public void update() {
+ // Try to keep track of currently open nodes and reopen them
+ FastStringSet openNodes = FastStringSet.create();
+ Iterator<Widget> it = iterator();
+ while (it.hasNext()) {
+ collectOpenNodes(it.next(), openNodes);
+ }
+
+ clear();
+
+ SimplePanel trees = new SimplePanel();
+
+ for (ApplicationConnection application : ApplicationConfiguration
+ .getRunningApplications()) {
+ ServerConnector uiConnector = application.getUIConnector();
+ Widget connectorTree = buildConnectorTree(uiConnector, openNodes);
+
+ trees.add(connectorTree);
+ }
+
+ add(trees);
+ }
+
+ /**
+ * Adds the captions of all open (non-leaf) nodes in the hierarchy tree
+ * recursively.
+ *
+ * @param widget
+ * the widget in which to search for open nodes (if SimpleTree)
+ * @param openNodes
+ * the set in which open nodes should be added
+ */
+ private void collectOpenNodes(Widget widget, FastStringSet openNodes) {
+ if (widget instanceof SimpleTree) {
+ SimpleTree tree = (SimpleTree) widget;
+ if (tree.isOpen()) {
+ openNodes.add(tree.getCaption());
+ } else {
+ // no need to look inside closed nodes
+ return;
+ }
+ }
+ if (widget instanceof HasWidgets) {
+ Iterator<Widget> it = ((HasWidgets) widget).iterator();
+ while (it.hasNext()) {
+ collectOpenNodes(it.next(), openNodes);
+ }
+ }
+ }
+
+ private Widget buildConnectorTree(final ServerConnector connector,
+ FastStringSet openNodes) {
+ String connectorString = Util.getConnectorString(connector);
+
+ List<ServerConnector> children = connector.getChildren();
+
+ Widget widget;
+ if (children == null || children.isEmpty()) {
+ // Leaf node, just add a label
+ Label label = new Label(connectorString);
+ label.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ Highlight.showOnly(connector);
+ showServerDebugInfo(connector);
+ }
+ });
+ widget = label;
+ } else {
+ SimpleTree tree = new SimpleTree(connectorString) {
+ @Override
+ protected void select(ClickEvent event) {
+ super.select(event);
+ Highlight.showOnly(connector);
+ showServerDebugInfo(connector);
+ }
+ };
+ for (ServerConnector child : children) {
+ tree.add(buildConnectorTree(child, openNodes));
+ }
+ if (openNodes.contains(connectorString)) {
+ tree.open(false);
+ }
+ widget = tree;
+ }
+
+ if (widget instanceof HasDoubleClickHandlers) {
+ HasDoubleClickHandlers has = (HasDoubleClickHandlers) widget;
+ has.addDoubleClickHandler(new DoubleClickHandler() {
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ fireSelectEvent(connector);
+ }
+ });
+ }
+
+ return widget;
+ }
+
+ public void addListener(SelectConnectorListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(SelectConnectorListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void fireSelectEvent(ServerConnector connector) {
+ for (SelectConnectorListener listener : listeners) {
+ listener.select(connector, null);
+ }
+ }
+
+ /**
+ * Outputs debug information on the server - usually in the console of an
+ * IDE, with a clickable reference to the relevant code location.
+ *
+ * @since 7.1
+ * @param connector
+ * show debug info for this connector
+ */
+ static void showServerDebugInfo(ServerConnector connector) {
+ if (connector != null) {
+ connector.getConnection().getUIConnector()
+ .showServerDebugInfo(connector);
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/HierarchySection.java b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
index 90c9086d7d..616bf70c38 100644
--- a/client/src/com/vaadin/client/debug/internal/HierarchySection.java
+++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
@@ -15,23 +15,9 @@
*/
package com.vaadin.client.debug.internal;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.dom.client.Style.TextDecoration;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DoubleClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickHandler;
-import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.dom.client.MouseOverEvent;
-import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
@@ -40,28 +26,15 @@ import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConfiguration;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ComputedStyle;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.JsArrayObject;
import com.vaadin.client.ServerConnector;
-import com.vaadin.client.SimpleTree;
import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
import com.vaadin.client.ValueMap;
-import com.vaadin.client.metadata.NoDataException;
-import com.vaadin.client.metadata.Property;
-import com.vaadin.client.ui.AbstractConnector;
-import com.vaadin.client.ui.UnknownComponentConnector;
-import com.vaadin.shared.AbstractComponentState;
-import com.vaadin.shared.communication.SharedState;
/**
* Provides functionality for examining the UI component hierarchy.
@@ -73,7 +46,15 @@ public class HierarchySection implements Section {
private final DebugButton tabButton = new DebugButton(Icon.HIERARCHY,
"Examine component hierarchy");
- private final FlowPanel content = new FlowPanel();
+ private final SimplePanel content = new SimplePanel();
+
+ // TODO highlighting logic is split between these, should be refactored
+ private final FlowPanel helpPanel = new FlowPanel();
+ private final ConnectorInfoPanel infoPanel = new ConnectorInfoPanel();
+ private final HierarchyPanel hierarchyPanel = new HierarchyPanel();
+ private final OptimizedWidgetsetPanel widgetsetPanel = new OptimizedWidgetsetPanel();
+ private final AnalyzeLayoutsPanel analyzeLayoutsPanel = new AnalyzeLayoutsPanel();
+
private final FlowPanel controls = new FlowPanel();
private final Button find = new DebugButton(Icon.HIGHLIGHT,
@@ -125,79 +106,40 @@ public class HierarchySection implements Section {
}
});
+ hierarchyPanel.addListener(new SelectConnectorListener() {
+ @Override
+ public void select(ServerConnector connector, Element element) {
+ printState(connector, true);
+ }
+ });
+
+ analyzeLayoutsPanel.addListener(new SelectConnectorListener() {
+ @Override
+ public void select(ServerConnector connector, Element element) {
+ printState(connector, true);
+ }
+ });
+
content.setStylePrimaryName(VDebugWindow.STYLENAME + "-hierarchy");
+ initializeHelpPanel();
+ content.setWidget(helpPanel);
+ }
+
+ private void initializeHelpPanel() {
HTML info = new HTML(showHierarchy.getHTML() + " "
+ showHierarchy.getTitle() + "<br/>" + find.getHTML() + " "
+ find.getTitle() + "<br/>" + analyze.getHTML() + " "
+ analyze.getTitle() + "<br/>" + generateWS.getHTML() + " "
+ generateWS.getTitle() + "<br/>");
info.setStyleName(VDebugWindow.STYLENAME + "-info");
- content.add(info);
+ helpPanel.add(info);
}
private void showHierarchy() {
Highlight.hideAll();
- content.clear();
-
- // TODO Clearing and rebuilding the contents is not optimal for UX as
- // any previous expansions are lost.
- SimplePanel trees = new SimplePanel();
-
- for (ApplicationConnection application : ApplicationConfiguration
- .getRunningApplications()) {
- ServerConnector uiConnector = application.getUIConnector();
- Widget connectorTree = buildConnectorTree(uiConnector);
-
- trees.add(connectorTree);
- }
-
- content.add(trees);
- }
-
- private Widget buildConnectorTree(final ServerConnector connector) {
- String connectorString = Util.getConnectorString(connector);
-
- List<ServerConnector> children = connector.getChildren();
-
- Widget widget;
- if (children == null || children.isEmpty()) {
- // Leaf node, just add a label
- Label label = new Label(connectorString);
- label.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- Highlight.showOnly(connector);
- Highlight.showServerDebugInfo(connector);
- }
- });
- widget = label;
- } else {
- SimpleTree tree = new SimpleTree(connectorString) {
- @Override
- protected void select(ClickEvent event) {
- super.select(event);
- Highlight.showOnly(connector);
- Highlight.showServerDebugInfo(connector);
- }
- };
- for (ServerConnector child : children) {
- tree.add(buildConnectorTree(child));
- }
- widget = tree;
- }
-
- if (widget instanceof HasDoubleClickHandlers) {
- HasDoubleClickHandlers has = (HasDoubleClickHandlers) widget;
- has.addDoubleClickHandler(new DoubleClickHandler() {
- @Override
- public void onDoubleClick(DoubleClickEvent event) {
- printState(connector, true);
- }
- });
- }
-
- return widget;
+ hierarchyPanel.update();
+ content.setWidget(hierarchyPanel);
}
@Override
@@ -226,302 +168,19 @@ public class HierarchySection implements Section {
}
private void generateWidgetset() {
-
- content.clear();
- HTML h = new HTML("Getting used connectors");
- content.add(h);
-
- String s = "";
- for (ApplicationConnection ac : ApplicationConfiguration
- .getRunningApplications()) {
- ApplicationConfiguration conf = ac.getConfiguration();
- s += "<h1>Used connectors for " + conf.getServiceUrl() + "</h1>";
-
- for (String connectorName : getUsedConnectorNames(conf)) {
- s += connectorName + "<br/>";
- }
-
- s += "<h2>To make an optimized widgetset based on these connectors, do:</h2>";
- s += "<h3>1. Add to your widgetset.gwt.xml file:</h2>";
- s += "<textarea rows=\"3\" style=\"width:90%\">";
- s += "<generate-with class=\"OptimizedConnectorBundleLoaderFactory\">\n";
- s += " <when-type-assignable class=\"com.vaadin.client.metadata.ConnectorBundleLoader\" />\n";
- s += "</generate-with>";
- s += "</textarea>";
-
- s += "<h3>2. Add the following java file to your project:</h2>";
- s += "<textarea rows=\"5\" style=\"width:90%\">";
- s += generateOptimizedWidgetSet(getUsedConnectorNames(conf));
- s += "</textarea>";
- s += "<h3>3. Recompile widgetset</h2>";
-
- }
-
- h.setHTML(s);
- }
-
- private Set<String> getUsedConnectorNames(
- ApplicationConfiguration configuration) {
- int tag = 0;
- Set<String> usedConnectors = new HashSet<String>();
- while (true) {
- String serverSideClass = configuration
- .getServerSideClassNameForTag(tag);
- if (serverSideClass == null) {
- break;
- }
- Class<? extends ServerConnector> connectorClass = configuration
- .getConnectorClassByEncodedTag(tag);
- if (connectorClass == null) {
- break;
- }
-
- if (connectorClass != UnknownComponentConnector.class) {
- usedConnectors.add(connectorClass.getName());
- }
- tag++;
- if (tag > 10000) {
- // Sanity check
- VConsole.error("Search for used connector classes was forcefully terminated");
- break;
- }
- }
- return usedConnectors;
- }
-
- public String generateOptimizedWidgetSet(Set<String> usedConnectors) {
- String s = "import java.util.HashSet;\n";
- s += "import java.util.Set;\n";
-
- s += "import com.google.gwt.core.ext.typeinfo.JClassType;\n";
- s += "import com.vaadin.client.ui.ui.UIConnector;\n";
- s += "import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;\n";
- s += "import com.vaadin.shared.ui.Connect.LoadStyle;\n\n";
-
- s += "public class OptimizedConnectorBundleLoaderFactory extends\n";
- s += " ConnectorBundleLoaderFactory {\n";
- s += " private Set<String> eagerConnectors = new HashSet<String>();\n";
- s += " {\n";
- for (String c : usedConnectors) {
- s += " eagerConnectors.add(" + c
- + ".class.getName());\n";
- }
- s += " }\n";
- s += "\n";
- s += " @Override\n";
- s += " protected LoadStyle getLoadStyle(JClassType connectorType) {\n";
- s += " if (eagerConnectors.contains(connectorType.getQualifiedBinaryName())) {\n";
- s += " return LoadStyle.EAGER;\n";
- s += " } else {\n";
- s += " // Loads all other connectors immediately after the initial view has\n";
- s += " // been rendered\n";
- s += " return LoadStyle.DEFERRED;\n";
- s += " }\n";
- s += " }\n";
- s += "}\n";
-
- return s;
+ widgetsetPanel.update();
+ content.setWidget(widgetsetPanel);
}
private void analyzeLayouts() {
- content.clear();
- content.add(new Label("Analyzing layouts..."));
- List<ApplicationConnection> runningApplications = ApplicationConfiguration
- .getRunningApplications();
- for (ApplicationConnection applicationConnection : runningApplications) {
- applicationConnection.analyzeLayouts();
- }
+ analyzeLayoutsPanel.update();
+ content.setWidget(analyzeLayoutsPanel);
}
@Override
public void meta(ApplicationConnection ac, ValueMap meta) {
- content.clear();
- JsArray<ValueMap> valueMapArray = meta
- .getJSValueMapArray("invalidLayouts");
- int size = valueMapArray.length();
-
- if (size > 0) {
- SimpleTree root = new SimpleTree("Layouts analyzed, " + size
- + " top level problems");
- for (int i = 0; i < size; i++) {
- printLayoutError(ac, valueMapArray.get(i), root);
- }
- root.open(false);
- content.add(root);
- } else {
- content.add(new Label("Layouts analyzed, no top level problems"));
- }
-
- Set<ComponentConnector> zeroHeightComponents = new HashSet<ComponentConnector>();
- Set<ComponentConnector> zeroWidthComponents = new HashSet<ComponentConnector>();
- findZeroSizeComponents(zeroHeightComponents, zeroWidthComponents,
- ac.getUIConnector());
- if (zeroHeightComponents.size() > 0 || zeroWidthComponents.size() > 0) {
- content.add(new HTML("<h4> Client side notifications</h4>"
- + " <em>The following relative sized components were "
- + "rendered to a zero size container on the client side."
- + " Note that these are not necessarily invalid "
- + "states, but reported here as they might be.</em>"));
- if (zeroHeightComponents.size() > 0) {
- content.add(new HTML(
- "<p><strong>Vertically zero size:</strong></p>"));
- printClientSideDetectedIssues(zeroHeightComponents, ac);
- }
- if (zeroWidthComponents.size() > 0) {
- content.add(new HTML(
- "<p><strong>Horizontally zero size:</strong></p>"));
- printClientSideDetectedIssues(zeroWidthComponents, ac);
- }
- }
-
- }
-
- private void printClientSideDetectedIssues(
- Set<ComponentConnector> zeroSized, ApplicationConnection ac) {
-
- // keep track of already highlighted parents
- HashSet<String> parents = new HashSet<String>();
-
- for (final ComponentConnector connector : zeroSized) {
- final ServerConnector parent = connector.getParent();
- final String parentId = parent.getConnectorId();
-
- final Label errorDetails = new Label(Util.getSimpleName(connector)
- + "[" + connector.getConnectorId() + "]" + " inside "
- + Util.getSimpleName(parent));
-
- if (parent instanceof ComponentConnector) {
- final ComponentConnector parentConnector = (ComponentConnector) parent;
- if (!parents.contains(parentId)) {
- parents.add(parentId);
- Highlight.show(parentConnector, "yellow");
- }
-
- errorDetails.addMouseOverHandler(new MouseOverHandler() {
- @Override
- public void onMouseOver(MouseOverEvent event) {
- Highlight.hideAll();
- Highlight.show(parentConnector, "yellow");
- Highlight.show(connector);
- errorDetails.getElement().getStyle()
- .setTextDecoration(TextDecoration.UNDERLINE);
- }
- });
- errorDetails.addMouseOutHandler(new MouseOutHandler() {
- @Override
- public void onMouseOut(MouseOutEvent event) {
- Highlight.hideAll();
- errorDetails.getElement().getStyle()
- .setTextDecoration(TextDecoration.NONE);
- }
- });
- errorDetails.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- printState(connector, true);
- }
- });
-
- }
-
- Highlight.show(connector);
- content.add(errorDetails);
-
- }
- }
-
- private void printLayoutError(ApplicationConnection ac, ValueMap valueMap,
- SimpleTree root) {
- final String pid = valueMap.getString("id");
-
- // find connector
- final ComponentConnector connector = (ComponentConnector) ConnectorMap
- .get(ac).getConnector(pid);
-
- if (connector == null) {
- root.add(new SimpleTree("[" + pid + "] NOT FOUND"));
- return;
- }
-
- Highlight.show(connector);
-
- final SimpleTree errorNode = new SimpleTree(
- Util.getSimpleName(connector) + " id: " + pid);
- errorNode.addDomHandler(new MouseOverHandler() {
- @Override
- public void onMouseOver(MouseOverEvent event) {
- Highlight.showOnly(connector);
- ((Widget) event.getSource()).getElement().getStyle()
- .setTextDecoration(TextDecoration.UNDERLINE);
- }
- }, MouseOverEvent.getType());
- errorNode.addDomHandler(new MouseOutHandler() {
- @Override
- public void onMouseOut(MouseOutEvent event) {
- Highlight.hideAll();
- ((Widget) event.getSource()).getElement().getStyle()
- .setTextDecoration(TextDecoration.NONE);
- }
- }, MouseOutEvent.getType());
-
- errorNode.addDomHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- if (event.getNativeEvent().getEventTarget().cast() == errorNode
- .getElement().getChild(1).cast()) {
- printState(connector, true);
- }
- }
- }, ClickEvent.getType());
-
- VerticalPanel errorDetails = new VerticalPanel();
-
- if (valueMap.containsKey("heightMsg")) {
- errorDetails.add(new Label("Height problem: "
- + valueMap.getString("heightMsg")));
- }
- if (valueMap.containsKey("widthMsg")) {
- errorDetails.add(new Label("Width problem: "
- + valueMap.getString("widthMsg")));
- }
- if (errorDetails.getWidgetCount() > 0) {
- errorNode.add(errorDetails);
- }
- if (valueMap.containsKey("subErrors")) {
- HTML l = new HTML(
- "<em>Expand this node to show problems that may be dependent on this problem.</em>");
- errorDetails.add(l);
- JsArray<ValueMap> suberrors = valueMap
- .getJSValueMapArray("subErrors");
- for (int i = 0; i < suberrors.length(); i++) {
- ValueMap value = suberrors.get(i);
- printLayoutError(ac, value, errorNode);
- }
-
- }
- root.add(errorNode);
- }
-
- private void findZeroSizeComponents(
- Set<ComponentConnector> zeroHeightComponents,
- Set<ComponentConnector> zeroWidthComponents,
- ComponentConnector connector) {
- Widget widget = connector.getWidget();
- ComputedStyle computedStyle = new ComputedStyle(widget.getElement());
- if (computedStyle.getIntProperty("height") == 0) {
- zeroHeightComponents.add(connector);
- }
- if (computedStyle.getIntProperty("width") == 0) {
- zeroWidthComponents.add(connector);
- }
- List<ServerConnector> children = connector.getChildren();
- for (ServerConnector serverConnector : children) {
- if (serverConnector instanceof ComponentConnector) {
- findZeroSizeComponents(zeroHeightComponents,
- zeroWidthComponents,
- (ComponentConnector) serverConnector);
- }
- }
+ // show the results of analyzeLayouts
+ analyzeLayoutsPanel.meta(ac, meta);
}
@Override
@@ -561,60 +220,11 @@ public class HierarchySection implements Section {
private void printState(ServerConnector connector, boolean serverDebug) {
Highlight.showOnly(connector);
if (serverDebug) {
- Highlight.showServerDebugInfo(connector);
+ HierarchyPanel.showServerDebugInfo(connector);
}
- SharedState state = connector.getState();
-
- Set<String> ignoreProperties = new HashSet<String>();
- ignoreProperties.add("id");
-
- String html = getRowHTML("Id", connector.getConnectorId());
- html += getRowHTML("Connector", Util.getSimpleName(connector));
-
- if (connector instanceof ComponentConnector) {
- ComponentConnector component = (ComponentConnector) connector;
-
- ignoreProperties.addAll(Arrays.asList("caption", "description",
- "width", "height"));
-
- AbstractComponentState componentState = component.getState();
-
- html += getRowHTML("Widget",
- Util.getSimpleName(component.getWidget()));
- html += getRowHTML("Caption", componentState.caption);
- html += getRowHTML("Description", componentState.description);
- html += getRowHTML("Width", componentState.width + " (actual: "
- + component.getWidget().getOffsetWidth() + "px)");
- html += getRowHTML("Height", componentState.height + " (actual: "
- + component.getWidget().getOffsetHeight() + "px)");
- }
-
- try {
- JsArrayObject<Property> properties = AbstractConnector
- .getStateType(connector).getPropertiesAsArray();
- for (int i = 0; i < properties.size(); i++) {
- Property property = properties.get(i);
- String name = property.getName();
- if (!ignoreProperties.contains(name)) {
- html += getRowHTML(property.getDisplayName(),
- property.getValue(state));
- }
- }
- } catch (NoDataException e) {
- html += "<div>Could not read state, error has been logged to the console</div>";
- VConsole.error(e);
- }
-
- content.clear();
- content.add(new HTML(html));
- }
-
- private String getRowHTML(String caption, Object value) {
- return "<div class=\"" + VDebugWindow.STYLENAME
- + "-row\"><span class=\"caption\">" + caption
- + "</span><span class=\"value\">"
- + Util.escapeHTML(String.valueOf(value)) + "</span></div>";
+ infoPanel.update(connector);
+ content.setWidget(infoPanel);
}
private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() {
@@ -634,7 +244,7 @@ public class HierarchySection implements Section {
.getNativeEvent().getClientX(), event.getNativeEvent()
.getClientY());
if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) {
- content.clear();
+ infoPanel.clear();
return;
}
@@ -654,7 +264,7 @@ public class HierarchySection implements Section {
return;
}
}
- content.clear();
+ infoPanel.clear();
}
if (event.getTypeInt() == Event.ONCLICK) {
Highlight.hideAll();
diff --git a/client/src/com/vaadin/client/debug/internal/Highlight.java b/client/src/com/vaadin/client/debug/internal/Highlight.java
index 3c1af445a9..5ee3a25e2c 100644
--- a/client/src/com/vaadin/client/debug/internal/Highlight.java
+++ b/client/src/com/vaadin/client/debug/internal/Highlight.java
@@ -144,20 +144,55 @@ public class Highlight {
*/
static Element show(Widget widget, String color) {
if (widget != null) {
+ show(widget.getElement(), color);
+ }
+ return null;
+ }
+
+ /**
+ * Highlights the given {@link Element}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param element
+ * Element to highlight
+ * @return Highlight element
+ */
+ static Element show(Element element) {
+ return show(element, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlight the given {@link Element} using the given color.
+ * <p>
+ * Pass the returned highlight {@link Element} to {@link #hide(Element)} to
+ * remove this particular highlight.
+ * </p>
+ *
+ * @param element
+ * Element to highlight
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(Element element, String color) {
+ if (element != null) {
if (highlights == null) {
highlights = new HashSet<Element>();
}
Element highlight = DOM.createDiv();
Style style = highlight.getStyle();
- style.setTop(widget.getAbsoluteTop(), Unit.PX);
- style.setLeft(widget.getAbsoluteLeft(), Unit.PX);
- int width = widget.getOffsetWidth();
+ style.setTop(element.getAbsoluteTop(), Unit.PX);
+ style.setLeft(element.getAbsoluteLeft(), Unit.PX);
+ int width = element.getOffsetWidth();
if (width < MIN_WIDTH) {
width = MIN_WIDTH;
}
style.setWidth(width, Unit.PX);
- int height = widget.getOffsetHeight();
+ int height = element.getOffsetHeight();
if (height < MIN_HEIGHT) {
height = MIN_HEIGHT;
}
@@ -207,19 +242,4 @@ public class Highlight {
}
}
- /**
- * Outputs debug information on the server - usually in the console of an
- * IDE, with a clickable reference to the relevant code location.
- *
- * @since 7.1
- * @param connector
- * show debug info for this connector
- */
- static void showServerDebugInfo(ServerConnector connector) {
- if (connector != null) {
- connector.getConnection().getUIConnector()
- .showServerDebugInfo(connector);
- }
- }
-
}
diff --git a/client/src/com/vaadin/client/debug/internal/Icon.java b/client/src/com/vaadin/client/debug/internal/Icon.java
index cc2ef3b348..9ef6d833e2 100644
--- a/client/src/com/vaadin/client/debug/internal/Icon.java
+++ b/client/src/com/vaadin/client/debug/internal/Icon.java
@@ -32,6 +32,8 @@ public enum Icon {
LOG("&#xf0c9;"), //
OPTIMIZE("&#xf0d0;"), //
HIERARCHY("&#xf0e8;"), //
+ // TODO create more appropriate icon
+ SELECTOR("&#x2263;"), //
MENU("&#xf013;"), //
NETWORK("&#xf0ec;"), //
ANALYZE("&#xf0f0;"), //
diff --git a/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java b/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java
new file mode 100644
index 0000000000..a8d8aad888
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.UnknownComponentConnector;
+
+/**
+ * Optimized widgetset view panel of the debug window.
+ *
+ * @since 7.1.4
+ */
+public class OptimizedWidgetsetPanel extends FlowPanel {
+
+ /**
+ * Update the panel contents based on the connectors that have been used so
+ * far on this execution of the application.
+ */
+ public void update() {
+ clear();
+ HTML h = new HTML("Getting used connectors");
+ add(h);
+
+ String s = "";
+ for (ApplicationConnection ac : ApplicationConfiguration
+ .getRunningApplications()) {
+ ApplicationConfiguration conf = ac.getConfiguration();
+ s += "<h1>Used connectors for " + conf.getServiceUrl() + "</h1>";
+
+ for (String connectorName : getUsedConnectorNames(conf)) {
+ s += connectorName + "<br/>";
+ }
+
+ s += "<h2>To make an optimized widgetset based on these connectors, do:</h2>";
+ s += "<h3>1. Add to your widgetset.gwt.xml file:</h2>";
+ s += "<textarea rows=\"3\" style=\"width:90%\">";
+ s += "<generate-with class=\"OptimizedConnectorBundleLoaderFactory\">\n";
+ s += " <when-type-assignable class=\"com.vaadin.client.metadata.ConnectorBundleLoader\" />\n";
+ s += "</generate-with>";
+ s += "</textarea>";
+
+ s += "<h3>2. Add the following java file to your project:</h2>";
+ s += "<textarea rows=\"5\" style=\"width:90%\">";
+ s += generateOptimizedWidgetSet(getUsedConnectorNames(conf));
+ s += "</textarea>";
+ s += "<h3>3. Recompile widgetset</h2>";
+
+ }
+
+ h.setHTML(s);
+ }
+
+ private Set<String> getUsedConnectorNames(
+ ApplicationConfiguration configuration) {
+ int tag = 0;
+ Set<String> usedConnectors = new HashSet<String>();
+ while (true) {
+ String serverSideClass = configuration
+ .getServerSideClassNameForTag(tag);
+ if (serverSideClass == null) {
+ break;
+ }
+ Class<? extends ServerConnector> connectorClass = configuration
+ .getConnectorClassByEncodedTag(tag);
+ if (connectorClass == null) {
+ break;
+ }
+
+ if (connectorClass != UnknownComponentConnector.class) {
+ usedConnectors.add(connectorClass.getName());
+ }
+ tag++;
+ if (tag > 10000) {
+ // Sanity check
+ VConsole.error("Search for used connector classes was forcefully terminated");
+ break;
+ }
+ }
+ return usedConnectors;
+ }
+
+ public String generateOptimizedWidgetSet(Set<String> usedConnectors) {
+ String s = "import java.util.HashSet;\n";
+ s += "import java.util.Set;\n";
+
+ s += "import com.google.gwt.core.ext.typeinfo.JClassType;\n";
+ s += "import com.vaadin.client.ui.ui.UIConnector;\n";
+ s += "import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;\n";
+ s += "import com.vaadin.shared.ui.Connect.LoadStyle;\n\n";
+
+ s += "public class OptimizedConnectorBundleLoaderFactory extends\n";
+ s += " ConnectorBundleLoaderFactory {\n";
+ s += " private Set<String> eagerConnectors = new HashSet<String>();\n";
+ s += " {\n";
+ for (String c : usedConnectors) {
+ s += " eagerConnectors.add(" + c
+ + ".class.getName());\n";
+ }
+ s += " }\n";
+ s += "\n";
+ s += " @Override\n";
+ s += " protected LoadStyle getLoadStyle(JClassType connectorType) {\n";
+ s += " if (eagerConnectors.contains(connectorType.getQualifiedBinaryName())) {\n";
+ s += " return LoadStyle.EAGER;\n";
+ s += " } else {\n";
+ s += " // Loads all other connectors immediately after the initial view has\n";
+ s += " // been rendered\n";
+ s += " return LoadStyle.DEFERRED;\n";
+ s += " }\n";
+ s += " }\n";
+ s += "}\n";
+
+ return s;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java
new file mode 100644
index 0000000000..409f9d14ce
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.ServerConnector;
+
+/**
+ * Listener for the selection of a connector in the debug window.
+ *
+ * @since 7.1.4
+ */
+public interface SelectConnectorListener {
+ /**
+ * Listener method called when a connector has been selected. If a specific
+ * element of the connector was selected, it is also given.
+ *
+ * @param connector
+ * selected connector
+ * @param element
+ * selected element of the connector or null if unknown
+ */
+ public void select(ServerConnector connector, Element element);
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/SelectorPath.java b/client/src/com/vaadin/client/debug/internal/SelectorPath.java
new file mode 100644
index 0000000000..b8732e134e
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/SelectorPath.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.componentlocator.ComponentLocator;
+import com.vaadin.client.componentlocator.SelectorPredicate;
+
+/**
+ * A single segment of a selector path pointing to an Element.
+ * <p>
+ * This class should be considered internal to the framework and may change at
+ * any time.
+ * <p>
+ *
+ * @since 7.1.x
+ */
+public class SelectorPath {
+ private final String path;
+ private final Element element;
+ private final ComponentLocator locator;
+ private static Map<String, Integer> counter = new HashMap<String, Integer>();
+ private static Map<String, String> legacyNames = new HashMap<String, String>();
+
+ static {
+ legacyNames.put("FilterSelect", "ComboBox");
+ legacyNames.put("ScrollTable", "Table");
+ }
+
+ protected SelectorPath(ServerConnector c, Element e) {
+ element = e;
+ locator = new ComponentLocator(c.getConnection());
+ path = locator.getPathForElement(e);
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Element getElement() {
+ return element;
+ }
+
+ public ComponentLocator getLocator() {
+ return locator;
+ }
+
+ /**
+ * Generate ElementQuery code for Java. Fallback to By.vaadin(path) if
+ * dealing with LegacyLocator
+ *
+ * @return String containing Java code for finding the element described by
+ * path
+ */
+ public String getElementQuery() {
+ if (locator.isValidForLegacyLocator(path)) {
+ return getLegacyLocatorQuery();
+ }
+
+ String[] fragments;
+ String tmpPath = path;
+ List<SelectorPredicate> postFilters = SelectorPredicate
+ .extractPostFilterPredicates(path);
+ if (postFilters.size() > 0) {
+ tmpPath = tmpPath.substring(1, tmpPath.lastIndexOf(')'));
+ }
+
+ // Generate an ElementQuery
+ fragments = tmpPath.split("/");
+ String elementQueryString;
+ int index = 0;
+ for (SelectorPredicate p : postFilters) {
+ if (p.getIndex() > 0) {
+ index = p.getIndex();
+ }
+ }
+ if (index > 0) {
+ elementQueryString = ".get(" + index + ");";
+ } else {
+ elementQueryString = ".first();";
+ }
+ for (int i = 1; i < fragments.length; ++i) {
+ if (fragments[i].isEmpty()) {
+ // Recursive search has occasional empty fragments
+ continue;
+ }
+
+ // Get Element.class -name
+ String queryFragment = "";
+ String elementClass = getComponentName(fragments[i])
+ + "Element.class";
+ for (SelectorPredicate p : SelectorPredicate
+ .extractPredicates(fragments[i])) {
+ // Add in predicates like .caption and .id
+ queryFragment += "." + p.getName() + "(\"" + p.getValue()
+ + "\")";
+ }
+ if (i == fragments.length - 1) {
+ // Last element in path.
+ queryFragment = "$(" + elementClass + ")" + queryFragment;
+ } else {
+ // If followed by an empty fragment search is recursive
+ boolean recursive = fragments[i + 1].isEmpty();
+ if (recursive) {
+ queryFragment = ".in(" + elementClass + ")" + queryFragment;
+ } else {
+ queryFragment = ".childOf(" + elementClass + ")"
+ + queryFragment;
+ }
+ }
+ elementQueryString = queryFragment + elementQueryString;
+ }
+
+ if (!tmpPath.startsWith("//")) {
+ elementQueryString = "$" + elementQueryString;
+ }
+
+ // Return full Java variable assignment and eQuery
+ return generateJavaVariable(fragments[fragments.length - 1])
+ + elementQueryString;
+ }
+
+ /**
+ * @since
+ * @param frags
+ * @param i
+ * @return
+ */
+ protected String getComponentName(String fragment) {
+ return fragment.split("\\[")[0];
+ }
+
+ /**
+ * Generates a legacy locator for SelectorPath.
+ *
+ * @return String containing Java code for element search and assignment
+ */
+ private String getLegacyLocatorQuery() {
+ String[] frags = path.split("/");
+ String name = getComponentName(frags[frags.length - 1]).substring(1);
+
+ if (legacyNames.containsKey(name)) {
+ name = legacyNames.get(name);
+ }
+
+ name = getNameWithCount(name);
+
+ // Use direct path and elementX naming style.
+ return "WebElement " + name.substring(0, 1).toLowerCase()
+ + name.substring(1) + " = getDriver().findElement(By.vaadin(\""
+ + path + "\"));";
+ }
+
+ /**
+ * Get variable name with counter for given component name.
+ *
+ * @param name
+ * Component name
+ * @return name followed by count
+ */
+ protected String getNameWithCount(String name) {
+ if (!counter.containsKey(name)) {
+ counter.put(name, 0);
+ }
+ counter.put(name, counter.get(name) + 1);
+ name += counter.get(name);
+ return name;
+ }
+
+ /**
+ * Generate Java variable assignment from given selector fragment
+ *
+ * @param pathFragment
+ * Selector fragment
+ * @return piece of java code
+ */
+ private String generateJavaVariable(String pathFragment) {
+ // Get element type and predicates from fragment
+ List<SelectorPredicate> predicates = SelectorPredicate
+ .extractPredicates(pathFragment);
+ String elementType = pathFragment.split("\\[")[0];
+ String name = getNameFromPredicates(predicates, elementType);
+
+ if (name.equals(elementType)) {
+ name = getNameWithCount(name);
+ }
+
+ // Replace unusable characters
+ name = name.replaceAll("\\W", "");
+
+ // Lowercase the first character of name
+ return elementType + "Element " + name.substring(0, 1).toLowerCase()
+ + name.substring(1) + " = ";
+ }
+
+ /**
+ * Get variable name based on predicates. Fallback to elementType
+ *
+ * @param predicates
+ * Predicates related to element
+ * @param elementType
+ * Element type
+ * @return name for Variable
+ */
+ private String getNameFromPredicates(List<SelectorPredicate> predicates,
+ String elementType) {
+ String name = elementType;
+ for (SelectorPredicate p : predicates) {
+ if ("caption".equals(p.getName())) {
+ // Caption + elementType is a suitable name
+ name = p.getValue() + elementType;
+ } else if ("id".equals(p.getName())) {
+ // Just id. This is unique, use it.
+ return p.getValue();
+ }
+ }
+ return name;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
new file mode 100644
index 0000000000..5be75f2003
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Provides functionality for picking selectors for Vaadin TestBench.
+ *
+ * @since 7.1.x
+ * @author Vaadin Ltd
+ */
+public class TestBenchSection implements Section {
+
+ /**
+ * Selector widget showing a selector in a program-usable form.
+ */
+ private static class SelectorWidget extends HTML implements
+ MouseOverHandler, MouseOutHandler {
+ private final SelectorPath path;
+
+ public SelectorWidget(final SelectorPath path) {
+ this.path = path;
+
+ String html = "<div class=\"" + VDebugWindow.STYLENAME
+ + "-selector\"><span class=\"tb-selector\">"
+ + Util.escapeHTML(path.getElementQuery()) + "</span></div>";
+ setHTML(html);
+
+ addMouseOverHandler(this);
+ addMouseOutHandler(this);
+ }
+
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.hideAll();
+
+ Element element = path.getElement();
+ if (null != element) {
+ Highlight.show(element);
+ }
+ }
+
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ }
+ }
+
+ private final DebugButton tabButton = new DebugButton(Icon.WARNING,
+ "Pick Vaadin TestBench selectors");
+
+ private final FlowPanel content = new FlowPanel();
+
+ private final FlowPanel selectorPanel = new FlowPanel();
+ // map from full path to SelectorWidget to enable reuse of old selectors
+ private Map<SelectorPath, SelectorWidget> selectorWidgets = new HashMap<SelectorPath, SelectorWidget>();
+
+ private final FlowPanel controls = new FlowPanel();
+
+ private final Button find = new DebugButton(Icon.HIGHLIGHT,
+ "Pick an element and generate a query for it");
+
+ private final Button clear = new DebugButton(Icon.CLEAR,
+ "Clear current elements");
+
+ private HandlerRegistration highlightModeRegistration = null;
+
+ public TestBenchSection() {
+
+ controls.add(find);
+ find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ find.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleFind();
+ }
+ });
+
+ controls.add(clear);
+ clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ clear.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ clearResults();
+ }
+ });
+
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-testbench");
+ content.add(selectorPanel);
+ }
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+
+ }
+
+ @Override
+ public void hide() {
+ stopFind();
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ // NOP
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ // NOP
+ }
+
+ private boolean isFindMode() {
+ return (highlightModeRegistration != null);
+ }
+
+ private void toggleFind() {
+ if (isFindMode()) {
+ stopFind();
+ } else {
+ startFind();
+ }
+ }
+
+ private void startFind() {
+ Highlight.hideAll();
+ if (!isFindMode()) {
+ highlightModeRegistration = Event
+ .addNativePreviewHandler(highlightModeHandler);
+ find.addStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ }
+
+ private void stopFind() {
+ if (isFindMode()) {
+ highlightModeRegistration.removeHandler();
+ highlightModeRegistration = null;
+ find.removeStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ Highlight.hideAll();
+ }
+
+ private void pickSelector(ServerConnector connector, Element element) {
+
+ SelectorPath p = new SelectorPath(connector, Util
+ .findPaintable(connector.getConnection(), element).getWidget()
+ .getElement());
+ SelectorWidget w = new SelectorWidget(p);
+
+ content.add(w);
+ }
+
+ private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ stopFind();
+ Highlight.hideAll();
+ return;
+ }
+ if (event.getTypeInt() == Event.ONMOUSEMOVE
+ || event.getTypeInt() == Event.ONCLICK) {
+ Element eventTarget = Util.getElementFromPoint(event
+ .getNativeEvent().getClientX(), event.getNativeEvent()
+ .getClientY());
+ if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) {
+ if (isFindMode() && event.getTypeInt() == Event.ONCLICK) {
+ stopFind();
+ event.cancel();
+ }
+ return;
+ }
+
+ // make sure that not finding the highlight element only
+ Highlight.hideAll();
+
+ eventTarget = Util.getElementFromPoint(event.getNativeEvent()
+ .getClientX(), event.getNativeEvent().getClientY());
+ ComponentConnector connector = findConnector(eventTarget);
+
+ if (event.getTypeInt() == Event.ONMOUSEMOVE) {
+ if (connector != null) {
+ Highlight.showOnly(connector);
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ return;
+ }
+ } else if (event.getTypeInt() == Event.ONCLICK) {
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ if (connector != null) {
+ Highlight.showOnly(connector);
+ pickSelector(connector, eventTarget);
+ return;
+ }
+ }
+ }
+ event.cancel();
+ }
+
+ };
+
+ private ComponentConnector findConnector(Element element) {
+ for (ApplicationConnection a : ApplicationConfiguration
+ .getRunningApplications()) {
+ ComponentConnector connector = Util.getConnectorForElement(a, a
+ .getUIConnector().getWidget(), element);
+ if (connector == null) {
+ connector = Util.getConnectorForElement(a, RootPanel.get(),
+ element);
+ }
+ if (connector != null) {
+ return connector;
+ }
+ }
+ return null;
+ }
+
+ private void clearResults() {
+ content.clear();
+ }
+
+}
diff --git a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
index f1a9fa1ee7..8148010b54 100644
--- a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
+++ b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
@@ -15,11 +15,10 @@
*/
package com.vaadin.client.metadata;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import com.google.gwt.core.shared.GWT;
+import com.vaadin.client.FastStringMap;
import com.vaadin.client.metadata.AsyncBundleLoader.State;
public abstract class ConnectorBundleLoader {
@@ -28,8 +27,9 @@ public abstract class ConnectorBundleLoader {
private static ConnectorBundleLoader impl;
- private Map<String, AsyncBundleLoader> asyncBlockLoaders = new HashMap<String, AsyncBundleLoader>();
- private Map<String, String> identifierToBundle = new HashMap<String, String>();
+ private FastStringMap<AsyncBundleLoader> asyncBlockLoaders = FastStringMap
+ .create();
+ private FastStringMap<String> identifierToBundle = FastStringMap.create();
private final TypeDataStore datStore = new TypeDataStore();
diff --git a/client/src/com/vaadin/client/metadata/Property.java b/client/src/com/vaadin/client/metadata/Property.java
index 2e0ea49c88..64fbb79ca1 100644
--- a/client/src/com/vaadin/client/metadata/Property.java
+++ b/client/src/com/vaadin/client/metadata/Property.java
@@ -30,11 +30,11 @@ public class Property {
}
public Object getValue(Object bean) throws NoDataException {
- return TypeDataStore.getGetter(this).invoke(bean);
+ return TypeDataStore.getValue(this, bean);
}
public void setValue(Object bean, Object value) throws NoDataException {
- TypeDataStore.getSetter(this).invoke(bean, value);
+ TypeDataStore.setValue(this, bean, value);
}
public String getDelegateToWidgetMethodName() {
@@ -50,6 +50,10 @@ public class Property {
return TypeDataStore.getType(this);
}
+ public Type getBeanType() {
+ return bean;
+ }
+
/**
* The unique signature used to identify this property. The structure of the
* returned string may change without notice and should not be used for any
diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java
index aa37d75dc8..a3939b7994 100644
--- a/client/src/com/vaadin/client/metadata/TypeDataStore.java
+++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java
@@ -34,8 +34,6 @@ public class TypeDataStore {
.create();
private final FastStringMap<ProxyHandler> proxyHandlers = FastStringMap
.create();
- private final FastStringMap<JsArrayObject<Property>> properties = FastStringMap
- .create();
private final FastStringMap<JsArrayString> delegateToWidgetProperties = FastStringMap
.create();
@@ -46,12 +44,11 @@ public class TypeDataStore {
private final FastStringMap<Invoker> invokers = FastStringMap.create();
private final FastStringMap<Type[]> paramTypes = FastStringMap.create();
- private final FastStringMap<Type> propertyTypes = FastStringMap.create();
- private final FastStringMap<Invoker> setters = FastStringMap.create();
- private final FastStringMap<Invoker> getters = FastStringMap.create();
private final FastStringMap<String> delegateToWidget = FastStringMap
.create();
+ private final JavaScriptObject jsTypeData = JavaScriptObject.createObject();
+
public static TypeDataStore get() {
return ConnectorBundleLoader.get().getTypeDataStore();
}
@@ -69,6 +66,22 @@ public class TypeDataStore {
return class1;
}
+ // this is a very inefficient implementation for getting all the identifiers
+ // for a class
+ public FastStringSet findIdentifiersFor(Class<?> type) {
+ FastStringSet result = FastStringSet.create();
+
+ JsArrayString keys = identifiers.getKeys();
+ for (int i = 0; i < keys.length(); i++) {
+ String key = keys.get(i);
+ if (identifiers.get(key) == type) {
+ result.add(key);
+ }
+ }
+
+ return result;
+ }
+
public static Type getType(Class<?> clazz) {
return new Type(clazz);
}
@@ -101,19 +114,10 @@ public class TypeDataStore {
return invoker;
}
- public static Invoker getGetter(Property property) throws NoDataException {
- Invoker getter = get().getters.get(property.getSignature());
- if (getter == null) {
- throw new NoDataException("There is no getter for "
- + property.getSignature());
- }
-
- return getter;
- }
-
- public void setGetter(Class<?> clazz, String propertyName, Invoker invoker) {
- getters.put(new Property(getType(clazz), propertyName).getSignature(),
- invoker);
+ public static Object getValue(Property property, Object target)
+ throws NoDataException {
+ return getJsPropertyValue(get().jsTypeData, property.getBeanType()
+ .getBaseTypeName(), property.getName(), target);
}
public static String getDelegateToWidget(Property property) {
@@ -227,51 +231,31 @@ public class TypeDataStore {
public static JsArrayObject<Property> getPropertiesAsArray(Type type)
throws NoDataException {
- JsArrayObject<Property> properties = get().properties.get(type
- .getSignature());
- if (properties == null) {
- throw new NoDataException("No property list for "
- + type.getSignature());
- }
- return properties;
- }
+ JsArrayString names = getJsPropertyNames(get().jsTypeData,
+ type.getBaseTypeName());
- public void setProperties(Class<?> clazz, String[] propertyNames) {
+ // Create Property instances for each property name
JsArrayObject<Property> properties = JavaScriptObject.createArray()
.cast();
- Type type = getType(clazz);
- for (String name : propertyNames) {
- properties.add(new Property(type, name));
+ for (int i = 0; i < names.length(); i++) {
+ properties.add(new Property(type, names.get(i)));
}
- this.properties.put(type.getSignature(), properties);
- }
- public static Type getType(Property property) throws NoDataException {
- Type type = get().propertyTypes.get(property.getSignature());
- if (type == null) {
- throw new NoDataException("No return type for "
- + property.getSignature());
- }
- return type;
+ return properties;
}
- public void setPropertyType(Class<?> clazz, String propertName, Type type) {
- propertyTypes.put(
- new Property(getType(clazz), propertName).getSignature(), type);
+ public static Type getType(Property property) throws NoDataException {
+ return getJsPropertyType(get().jsTypeData, property.getBeanType()
+ .getBaseTypeName(), property.getName());
}
- public static Invoker getSetter(Property property) throws NoDataException {
- Invoker setter = get().setters.get(property.getSignature());
- if (setter == null) {
- throw new NoDataException("No setter for "
- + property.getSignature());
- }
- return setter;
+ public void setPropertyType(Class<?> clazz, String propertyName, Type type) {
+ setJsPropertyType(jsTypeData, clazz.getName(), propertyName, type);
}
- public void setSetter(Class<?> clazz, String propertyName, Invoker setter) {
- setters.put(new Property(getType(clazz), propertyName).getSignature(),
- setter);
+ public static void setValue(Property property, Object target, Object value) {
+ setJsPropertyValue(get().jsTypeData, property.getBeanType()
+ .getBaseTypeName(), property.getName(), target, value);
}
public void setSerializerFactory(Class<?> clazz, Invoker factory) {
@@ -288,6 +272,99 @@ public class TypeDataStore {
}
public static boolean hasProperties(Type type) {
- return get().properties.containsKey(type.getSignature());
+ return hasJsProperties(get().jsTypeData, type.getBaseTypeName());
}
+
+ public void setSuperClass(Class<?> baseClass, Class<?> superClass) {
+ String superClassName = superClass == null ? null : superClass
+ .getName();
+ setSuperClass(jsTypeData, baseClass.getName(), superClassName);
+ }
+
+ public void setPropertyData(Class<?> type, String propertyName,
+ JavaScriptObject propertyData) {
+ setPropertyData(jsTypeData, type.getName(), propertyName, propertyData);
+ }
+
+ private static native void setPropertyData(JavaScriptObject typeData,
+ String className, String propertyName, JavaScriptObject propertyData)
+ /*-{
+ typeData[className][propertyName] = propertyData;
+ }-*/;
+
+ /*
+ * This method sets up prototypes chain for <code>baseClassName</code>.
+ * Precondition is : <code>superClassName</code> had to be handled before
+ * its child <code>baseClassName</code>.
+ *
+ * It makes all properties defined in the <code>superClassName</code>
+ * available for <code>baseClassName</code> as well.
+ */
+ private static native void setSuperClass(JavaScriptObject typeData,
+ String baseClassName, String superClassName)
+ /*-{
+ var parentType = typeData[superClassName];
+ if (parentType !== undefined ){
+ var ctor = function () {};
+ ctor.prototype = parentType;
+ typeData[baseClassName] = new ctor;
+ }
+ else {
+ typeData[baseClassName] = {};
+ }
+ }-*/;
+
+ private static native boolean hasGetter(JavaScriptObject typeData,
+ String beanName, String propertyName)
+ /*-{
+ return typeData[beanName][propertyName].getter !== undefined;
+ }-*/;
+
+ private static native boolean hasSetter(JavaScriptObject typeData,
+ String beanName, String propertyName)
+ /*-{
+ return typeData[beanName][propertyName].setter !== undefined;
+ }-*/;
+
+ private static native Object getJsPropertyValue(JavaScriptObject typeData,
+ String beanName, String propertyName, Object beanInstance)
+ /*-{
+ return typeData[beanName][propertyName].getter(beanInstance);
+ }-*/;
+
+ private static native void setJsPropertyValue(JavaScriptObject typeData,
+ String beanName, String propertyName, Object beanInstance,
+ Object value)
+ /*-{
+ typeData[beanName][propertyName].setter(beanInstance, value);
+ }-*/;
+
+ private static native Type getJsPropertyType(JavaScriptObject typeData,
+ String beanName, String propertyName)
+ /*-{
+ return typeData[beanName][propertyName].type;
+ }-*/;
+
+ private static native void setJsPropertyType(JavaScriptObject typeData,
+ String beanName, String propertyName, Type type)
+ /*-{
+ typeData[beanName][propertyName].type = type;
+ }-*/;
+
+ private static native JsArrayString getJsPropertyNames(
+ JavaScriptObject typeData, String beanName)
+ /*-{
+ var names = [];
+ for(var name in typeData[beanName]) {
+ names.push(name);
+ }
+ return names;
+ }-*/;
+
+ private static native boolean hasJsProperties(JavaScriptObject typeData,
+ String beanName)
+ /*-{
+ return typeData[beanName] !== undefined ;
+ }-*/;
+
}
diff --git a/client/src/com/vaadin/client/ui/SubPartAware.java b/client/src/com/vaadin/client/ui/SubPartAware.java
index a7d72fab01..36959e7b1f 100644
--- a/client/src/com/vaadin/client/ui/SubPartAware.java
+++ b/client/src/com/vaadin/client/ui/SubPartAware.java
@@ -17,7 +17,7 @@ package com.vaadin.client.ui;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComponentLocator;
+import com.vaadin.client.componentlocator.ComponentLocator;
/**
* Interface implemented by {@link Widget}s which can provide identifiers for at
@@ -59,4 +59,4 @@ public interface SubPartAware {
*/
String getSubPartName(Element subElement);
-} \ No newline at end of file
+}
diff --git a/client/src/com/vaadin/client/ui/UnknownComponentConnector.java b/client/src/com/vaadin/client/ui/UnknownComponentConnector.java
index ca461eb640..b9b0388d9a 100644
--- a/client/src/com/vaadin/client/ui/UnknownComponentConnector.java
+++ b/client/src/com/vaadin/client/ui/UnknownComponentConnector.java
@@ -16,6 +16,8 @@
package com.vaadin.client.ui;
+import com.google.gwt.core.client.GWT;
+
public class UnknownComponentConnector extends AbstractComponentConnector {
@Override
@@ -31,7 +33,9 @@ public class UnknownComponentConnector extends AbstractComponentConnector {
public void setServerSideClassName(String serverClassName) {
getWidget()
.setCaption(
- "Widgetset does not contain implementation for "
+ "Widgetset '"
+ + GWT.getModuleName()
+ + "' does not contain implementation for "
+ serverClassName
+ ". Check its component connector's @Connect mapping, widgetsets "
+ "GWT module description file and re-compile your"
diff --git a/client/src/com/vaadin/client/ui/VAccordion.java b/client/src/com/vaadin/client/ui/VAccordion.java
index f87186fe37..ddfe9dbc2c 100644
--- a/client/src/com/vaadin/client/ui/VAccordion.java
+++ b/client/src/com/vaadin/client/ui/VAccordion.java
@@ -463,7 +463,6 @@ public class VAccordion extends VTabsheetBase {
}
@Override
- @SuppressWarnings("unchecked")
public Iterator<Widget> getWidgetIterator() {
return widgets.iterator();
}
diff --git a/client/src/com/vaadin/client/ui/VCalendarPanel.java b/client/src/com/vaadin/client/ui/VCalendarPanel.java
index 96678fd133..b043cd0ab7 100644
--- a/client/src/com/vaadin/client/ui/VCalendarPanel.java
+++ b/client/src/com/vaadin/client/ui/VCalendarPanel.java
@@ -170,8 +170,6 @@ public class VCalendarPanel extends FocusableFlexTable implements
private Resolution resolution = Resolution.YEAR;
- private int focusedRow;
-
private Timer mouseTimer;
private Date value;
@@ -256,7 +254,6 @@ public class VCalendarPanel extends FocusableFlexTable implements
if (curday.getDate().equals(date)) {
curday.addStyleDependentName(CN_FOCUSED);
focusedDay = curday;
- focusedRow = i;
return;
}
}
@@ -741,7 +738,6 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
if (curr.equals(focusedDate)) {
focusedDay = day;
- focusedRow = weekOfMonth;
if (hasFocus) {
day.addStyleDependentName(CN_FOCUSED);
}
@@ -1795,10 +1791,8 @@ public class VCalendarPanel extends FocusableFlexTable implements
* Updates the valus to correspond to the values in value
*/
public void updateTimes() {
- boolean selected = true;
if (value == null) {
value = new Date();
- selected = false;
}
if (getDateTimeService().isTwelveHourClock()) {
int h = value.getHours();
@@ -1833,10 +1827,6 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
- private int getMilliseconds() {
- return DateTimeService.getMilliseconds(value);
- }
-
private DateTimeService getDateTimeService() {
if (dateTimeService == null) {
dateTimeService = new DateTimeService();
@@ -2034,7 +2024,6 @@ public class VCalendarPanel extends FocusableFlexTable implements
private static final String SUBPART_HOUR_SELECT = "h";
private static final String SUBPART_MINUTE_SELECT = "m";
private static final String SUBPART_SECS_SELECT = "s";
- private static final String SUBPART_MSECS_SELECT = "ms";
private static final String SUBPART_AMPM_SELECT = "ampm";
private static final String SUBPART_DAY = "day";
private static final String SUBPART_MONTH_YEAR_HEADER = "header";
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
index 7efb5b8867..9bace9141c 100644
--- a/client/src/com/vaadin/client/ui/VFilterSelect.java
+++ b/client/src/com/vaadin/client/ui/VFilterSelect.java
@@ -1988,6 +1988,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
return tb.getElement();
} else if ("button".equals(subPart)) {
return popupOpener.getElement();
+ } else if ("popup".equals(subPart) && suggestionPopup.isAttached()) {
+ return suggestionPopup.getElement();
}
return null;
}
@@ -1998,6 +2000,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
return "textbox";
} else if (popupOpener.getElement().isOrHasChild(subElement)) {
return "button";
+ } else if (suggestionPopup.getElement().isOrHasChild(subElement)) {
+ return "popup";
}
return null;
}
diff --git a/client/src/com/vaadin/client/ui/VLabel.java b/client/src/com/vaadin/client/ui/VLabel.java
index 8acd653778..35f47d540a 100644
--- a/client/src/com/vaadin/client/ui/VLabel.java
+++ b/client/src/com/vaadin/client/ui/VLabel.java
@@ -18,7 +18,6 @@ package com.vaadin.client.ui;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HTML;
-import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.Util;
import com.vaadin.client.VTooltip;
@@ -28,8 +27,6 @@ public class VLabel extends HTML {
public static final String CLASSNAME = "v-label";
private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w";
- private ApplicationConnection connection;
-
public VLabel() {
super();
setStyleName(CLASSNAME);
@@ -71,9 +68,4 @@ public class VLabel extends HTML {
super.setText(text);
}
}
-
- /** For internal use only. May be removed or replaced in the future. */
- public void setConnection(ApplicationConnection client) {
- connection = client;
- }
}
diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java
index 9f84c16020..545af2ce83 100644
--- a/client/src/com/vaadin/client/ui/VOverlay.java
+++ b/client/src/com/vaadin/client/ui/VOverlay.java
@@ -241,10 +241,10 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
private void removeShadowIfPresent() {
if (isShadowAttached()) {
- shadow.removeFromParent();
-
// Remove event listener from the shadow
unsinkShadowEvents();
+
+ shadow.removeFromParent();
}
}
diff --git a/client/src/com/vaadin/client/ui/VPanel.java b/client/src/com/vaadin/client/ui/VPanel.java
index 6b02f079d1..ffeacade46 100644
--- a/client/src/com/vaadin/client/ui/VPanel.java
+++ b/client/src/com/vaadin/client/ui/VPanel.java
@@ -170,7 +170,6 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner,
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
- final Element target = DOM.eventGetTarget(event);
final int type = DOM.eventGetType(event);
if (type == Event.ONKEYDOWN && shortcutHandler != null) {
shortcutHandler.handleKeyboardEvent(event);
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
index 8bd875690b..bbf06bfec1 100644
--- a/client/src/com/vaadin/client/ui/VScrollTable.java
+++ b/client/src/com/vaadin/client/ui/VScrollTable.java
@@ -61,6 +61,8 @@ import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
@@ -123,7 +125,7 @@ import com.vaadin.shared.ui.table.TableConstants;
*/
public class VScrollTable extends FlowPanel implements HasWidgets,
ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
- ActionOwner {
+ ActionOwner, SubPartAware {
public static final String STYLENAME = "v-table";
@@ -991,6 +993,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
if (scrollBody != null) {
scrollBody.removeFromParent();
}
+
+ // Without this call the scroll position is messed up in IE even after
+ // the lazy scroller has set the scroll position to the first visible
+ // item
+ scrollBodyPanel.getScrollPosition();
+
scrollBody = createScrollBody();
scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
@@ -1054,6 +1062,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
if (uidl.hasVariable("selected")) {
final Set<String> selectedKeys = uidl
.getStringArrayVariableAsSet("selected");
+ removeUnselectedRowKeys(selectedKeys);
+
if (scrollBody != null) {
Iterator<Widget> iterator = scrollBody.iterator();
while (iterator.hasNext()) {
@@ -1096,6 +1106,16 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
return keyboardSelectionOverRowFetchInProgress;
}
+ private void removeUnselectedRowKeys(final Set<String> selectedKeys) {
+ List<String> unselectedKeys = new ArrayList<String>(0);
+ for (String key : selectedRowKeys) {
+ if (!selectedKeys.contains(key)) {
+ unselectedKeys.add(key);
+ }
+ }
+ selectedRowKeys.removeAll(unselectedKeys);
+ }
+
/** For internal use only. May be removed or replaced in the future. */
public void updateSortingProperties(UIDL uidl) {
oldSortColumn = sortColumn;
@@ -1121,7 +1141,28 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
}
}
+ private boolean lazyScrollerIsActive;
+
+ private void disableLazyScroller() {
+ lazyScrollerIsActive = false;
+ scrollBodyPanel.getElement().getStyle().clearOverflowX();
+ scrollBodyPanel.getElement().getStyle().clearOverflowY();
+ }
+
+ private void enableLazyScroller() {
+ Scheduler.get().scheduleDeferred(lazyScroller);
+ lazyScrollerIsActive = true;
+ // prevent scrolling to jump in IE11
+ scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN);
+ scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN);
+ }
+
+ private boolean isLazyScrollerActive() {
+ return lazyScrollerIsActive;
+ }
+
private ScheduledCommand lazyScroller = new ScheduledCommand() {
+
@Override
public void execute() {
if (firstvisible > 0) {
@@ -1134,6 +1175,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
.setScrollPosition(measureRowHeightOffset(firstvisible));
}
}
+ disableLazyScroller();
}
};
@@ -1152,7 +1194,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
// Only scroll if the first visible changes from the server side.
// Else we might unintentionally scroll even when the scroll
// position has not changed.
- Scheduler.get().scheduleDeferred(lazyScroller);
+ enableLazyScroller();
}
}
@@ -2172,7 +2214,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
isNewBody = false;
if (firstvisible > 0) {
- Scheduler.get().scheduleDeferred(lazyScroller);
+ enableLazyScroller();
}
if (enabled) {
@@ -5056,6 +5098,20 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
}
}
+ public int indexOf(Widget row) {
+ int relIx = -1;
+ for (int ix = 0; ix < renderedRows.size(); ix++) {
+ if (renderedRows.get(ix) == row) {
+ relIx = ix;
+ break;
+ }
+ }
+ if (relIx >= 0) {
+ return this.firstRendered + relIx;
+ }
+ return -1;
+ }
+
public class VScrollTableRow extends Panel implements ActionOwner {
private static final int TOUCHSCROLL_TIMEOUT = 100;
@@ -6037,7 +6093,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
private Element getEventTargetTdOrTr(Event event) {
final Element eventTarget = event.getEventTarget().cast();
Widget widget = Util.findWidget(eventTarget, null);
- final Element thisTrElement = getElement();
if (widget != this) {
/*
@@ -6884,6 +6939,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
@Override
public void onScroll(ScrollEvent event) {
+ // Do not handle scroll events while there is scroll initiated from
+ // server side which is not yet executed (#11454)
+ if (isLazyScrollerActive()) {
+ return;
+ }
+
scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
scrollTop = scrollBodyPanel.getScrollPosition();
/*
@@ -6932,8 +6993,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
}
firstRowInViewPort = calcFirstRowInViewPort();
- if (firstRowInViewPort > totalRows - pageLength) {
- firstRowInViewPort = totalRows - pageLength;
+ int maxFirstRow = totalRows - pageLength;
+ if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) {
+ firstRowInViewPort = maxFirstRow;
}
int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
@@ -7764,4 +7826,94 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
return this;
}
+ private static final String SUBPART_HEADER = "header";
+ private static final String SUBPART_FOOTER = "footer";
+ private static final String SUBPART_ROW = "row";
+ private static final String SUBPART_COL = "col";
+ /** Matches header[ix] - used for extracting the index of the targeted header cell */
+ private static final RegExp SUBPART_HEADER_REGEXP = RegExp
+ .compile(SUBPART_HEADER + "\\[(\\d+)\\]");
+ /** Matches footer[ix] - used for extracting the index of the targeted footer cell */
+ private static final RegExp SUBPART_FOOTER_REGEXP = RegExp
+ .compile(SUBPART_FOOTER + "\\[(\\d+)\\]");
+ /** Matches row[ix] - used for extracting the index of the targeted row */
+ private static final RegExp SUBPART_ROW_REGEXP = RegExp.compile(SUBPART_ROW
+ + "\\[(\\d+)]");
+ /** Matches col[ix] - used for extracting the index of the targeted column */
+ private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp
+ .compile(SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL + "\\[(\\d+)\\]");
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if (SUBPART_ROW_COL_REGEXP.test(subPart)) {
+ MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart);
+ int rowIx = Integer.valueOf(result.getGroup(1));
+ int colIx = Integer.valueOf(result.getGroup(2));
+ VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
+ if (row != null) {
+ Element rowElement = row.getElement();
+ if (colIx < rowElement.getChildCount()) {
+ return rowElement.getChild(colIx).getFirstChild().cast();
+ }
+ }
+
+ } else if (SUBPART_ROW_REGEXP.test(subPart)) {
+ MatchResult result = SUBPART_ROW_REGEXP.exec(subPart);
+ int rowIx = Integer.valueOf(result.getGroup(1));
+ VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
+ if (row != null) {
+ return row.getElement();
+ }
+
+ } else if (SUBPART_HEADER_REGEXP.test(subPart)) {
+ MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart);
+ int headerIx = Integer.valueOf(result.getGroup(1));
+ HeaderCell headerCell = tHead.getHeaderCell(headerIx);
+ if (headerCell != null) {
+ return headerCell.getElement();
+ }
+
+ } else if (SUBPART_FOOTER_REGEXP.test(subPart)) {
+ MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart);
+ int footerIx = Integer.valueOf(result.getGroup(1));
+ FooterCell footerCell = tFoot.getFooterCell(footerIx);
+ if (footerCell != null) {
+ return footerCell.getElement();
+ }
+ }
+ // Nothing found.
+ return null;
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ Widget widget = Util.findWidget(subElement, null);
+ if (widget instanceof HeaderCell) {
+ return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget)
+ + "]";
+ } else if (widget instanceof FooterCell) {
+ return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget)
+ + "]";
+ } else if (widget instanceof VScrollTableRow) {
+ // a cell in a row
+ VScrollTableRow row = (VScrollTableRow) widget;
+ int rowIx = scrollBody.indexOf(row);
+ if (rowIx >= 0) {
+ int colIx = -1;
+ for (int ix = 0; ix < row.getElement().getChildCount(); ix++) {
+ if (row.getElement().getChild(ix).isOrHasChild(subElement)) {
+ colIx = ix;
+ break;
+ }
+ }
+ if (colIx >= 0) {
+ return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "["
+ + colIx + "]";
+ }
+ return SUBPART_ROW + "[" + rowIx + "]";
+ }
+ }
+ // Nothing found.
+ return null;
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java
index 9ad518b85b..82eb9e7694 100644
--- a/client/src/com/vaadin/client/ui/VTabsheet.java
+++ b/client/src/com/vaadin/client/ui/VTabsheet.java
@@ -135,7 +135,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
SelectedValue.FALSE);
div = DOM.createDiv();
- focusImpl.setTabIndex(td, -1);
+ setTabulatorIndex(-1);
setStyleName(div, DIV_CLASSNAME);
DOM.appendChild(td, div);
@@ -213,7 +213,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
public void setTabulatorIndex(int tabIndex) {
- focusImpl.setTabIndex(td, tabIndex);
+ getElement().setTabIndex(tabIndex);
}
public boolean isClosable() {
@@ -313,11 +313,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
private boolean closable = false;
private Element closeButton;
private Tab tab;
- private ApplicationConnection client;
TabCaption(Tab tab, ApplicationConnection client) {
super(client);
- this.client = client;
this.tab = tab;
AriaHelper.ensureHasId(getElement());
@@ -488,6 +486,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
int index = getWidgetIndex(caption.getParent());
+ navigateTab(getTabsheet().focusedTabIndex, index);
+ getTabsheet().focusedTabIndex = index;
+ getTabsheet().focusedTab = getTab(index);
getTabsheet().focus();
getTabsheet().loadTabSheet(index);
}
@@ -702,15 +703,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) {
tb.selectTab(tabIndex);
- // If this TabSheet already has focus, set the new selected tab
- // as focused.
- if (focusedTab != null) {
- focusedTab = tb.getTab(tabIndex);
- focusedTab.focus();
- }
-
activeTabIndex = tabIndex;
- focusedTabIndex = tabIndex;
addStyleDependentName("loading");
// Hide the current contents so a loading indicator can be shown
@@ -722,6 +715,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
client.updateVariable(id, "selected", tabKeys.get(tabIndex)
.toString(), true);
waitingForResponse = true;
+
+ tb.getTab(tabIndex).focus(); // move keyboard focus to active tab
}
}
@@ -952,6 +947,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
if (tab == null) {
tab = tb.addTab();
}
+ if (selected) {
+ tb.selectTab(index);
+ renderContent(tabUidl.getChildUIDL(0));
+ }
tab.updateFromUIDL(tabUidl);
tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
tab.setHiddenOnServer(hidden);
@@ -968,11 +967,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
* and tabs won't be too narrow in certain browsers
*/
tab.recalculateCaptionWidth();
-
- if (selected) {
- renderContent(tabUidl.getChildUIDL(0));
- tb.selectTab(index);
- }
}
/**
@@ -1089,16 +1083,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
DOM.setElementProperty(scrollerNext, "className",
SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
+
+ // the active tab should be focusable if and only if it is visible
+ boolean isActiveTabVisible = scrollerIndex <= activeTabIndex
+ && !isClipped(tb.selected);
+ tb.selected.setTabulatorIndex(isActiveTabVisible ? tabulatorIndex
+ : -1);
+
} else {
DOM.setStyleAttribute(scroller, "display", "none");
}
if (BrowserInfo.get().isSafari()) {
- // fix tab height for safari, bugs sometimes if tabs contain icons
- String property = tabs.getStyle().getProperty("height");
- if (property == null || property.equals("")) {
- tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
- }
/*
* another hack for webkits. tabscroller sometimes drops without
* "shaking it" reproducable in
@@ -1194,7 +1190,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public void onBlur(BlurEvent event) {
getApplicationConnection().getVTooltip().hideTooltip();
- if (focusedTab != null && event.getSource() instanceof Tab) {
+ if (focusedTab != null && focusedTab == event.getSource()) {
focusedTab.removeAssistiveDescription();
focusedTab = null;
if (client.hasEventListeners(this, EventId.BLUR)) {
@@ -1300,13 +1296,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
if (isScrolledTabs()) {
- // Scroll until the new active tab is visible
- int newScrollerIndex = scrollerIndex;
- while (tb.getTab(focusedTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
- && newScrollerIndex != -1) {
- newScrollerIndex = tb.scrollLeft(newScrollerIndex);
+ // Scroll until the new focused tab is visible
+ while (!tb.getTab(focusedTabIndex).isVisible()) {
+ scrollerIndex = tb.scrollLeft(scrollerIndex);
}
- scrollerIndex = newScrollerIndex;
updateTabScroller();
}
}
diff --git a/client/src/com/vaadin/client/ui/VTreeTable.java b/client/src/com/vaadin/client/ui/VTreeTable.java
index 097b9c7ab2..54c9c2d30c 100644
--- a/client/src/com/vaadin/client/ui/VTreeTable.java
+++ b/client/src/com/vaadin/client/ui/VTreeTable.java
@@ -131,7 +131,7 @@ public class VTreeTable extends VScrollTable {
private int indentWidth = -1;
private int maxIndent = 0;
- VTreeTableScrollBody() {
+ protected VTreeTableScrollBody() {
super();
}
diff --git a/client/src/com/vaadin/client/ui/VUpload.java b/client/src/com/vaadin/client/ui/VUpload.java
index c08d75e9b7..8e55387d39 100644
--- a/client/src/com/vaadin/client/ui/VUpload.java
+++ b/client/src/com/vaadin/client/ui/VUpload.java
@@ -295,10 +295,13 @@ public class VUpload extends SimplePanel {
/** For internal use only. May be removed or replaced in the future. */
public void submit() {
- if (fu.getFilename().length() == 0 || submitted || !enabled) {
- VConsole.log("Submit cancelled (disabled, no file or already submitted)");
+ if (submitted || !enabled) {
+ VConsole.log("Submit cancelled (disabled or already submitted)");
return;
}
+ if (fu.getFilename().length() == 0) {
+ VConsole.log("Submitting empty selection (no file)");
+ }
// flush possibly pending variable changes, so they will be handled
// before upload
client.sendPendingVariableChanges();
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
index 964adfe303..705787d6c8 100644
--- a/client/src/com/vaadin/client/ui/VWindow.java
+++ b/client/src/com/vaadin/client/ui/VWindow.java
@@ -27,6 +27,7 @@ import com.google.gwt.aria.client.RelevantValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
@@ -83,6 +84,8 @@ public class VWindow extends VWindowOverlay implements
public static final String CLASSNAME = "v-window";
+ private static final String MODAL_WINDOW_OPEN_CLASSNAME = "v-modal-window-open";
+
private static final int STACKING_OFFSET_PIXELS = 15;
public static final int Z_INDEX = 10000;
@@ -725,10 +728,14 @@ public class VWindow extends VWindowOverlay implements
getOverlayContainer().appendChild(getModalityCurtain());
}
+ Document.get().getBody().addClassName(MODAL_WINDOW_OPEN_CLASSNAME);
}
private void hideModalityCurtain() {
+ Document.get().getBody().removeClassName(MODAL_WINDOW_OPEN_CLASSNAME);
+
modalityCurtain.removeFromParent();
+
if (BrowserInfo.get().isIE()) {
// IE leaks memory in certain cases unless we release the reference
// (#9197)
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
index 39de122694..344b5ce739 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
@@ -42,7 +42,6 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.vaadin.client.Util;
-import com.vaadin.client.ui.VCalendar;
import com.vaadin.shared.ui.calendar.DateConstants;
/**
@@ -105,7 +104,6 @@ public class DateCellDayEvent extends FocusableHTML implements
eventContent.addClassName("v-calendar-event-content");
getElement().appendChild(eventContent);
- VCalendar calendar = weekGrid.getCalendar();
if (weekGrid.getCalendar().isEventResizeAllowed()) {
topResizeBar = DOM.createDiv();
bottomResizeBar = DOM.createDiv();
@@ -189,9 +187,11 @@ public class DateCellDayEvent extends FocusableHTML implements
String escapedCaption = Util.escapeHTML(calendarEvent.getCaption());
String timeAsText = calendarEvent.getTimeAsText();
if (bigMode) {
- innerHtml = "<span>" + timeAsText + "</span><br />" + escapedCaption;
+ innerHtml = "<span>" + timeAsText + "</span><br />"
+ + escapedCaption;
} else {
- innerHtml = "<span>" + timeAsText + "<span>:</span></span> " + escapedCaption;
+ innerHtml = "<span>" + timeAsText + "<span>:</span></span> "
+ + escapedCaption;
}
caption.setInnerHTML(innerHtml);
eventContent.setInnerHTML("");
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
index 6233e8111e..58b5fafa7f 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
@@ -72,8 +72,6 @@ public class DayToolbar extends HorizontalPanel implements ClickHandler {
setCellWidth(nextLabel, MARGINRIGHT + "px");
setCellHorizontalAlignment(nextLabel, ALIGN_RIGHT);
int cellw = width / (count - 2);
- int remain = width % (count - 2);
- int cellw2 = cellw + 1;
if (cellw > 0) {
int[] cellWidths = VCalendar
.distributeSize(width, count - 2, 0);
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
index df9bc42d2a..3b1c774793 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
@@ -35,7 +35,6 @@ public class MonthGrid extends FocusableGrid implements KeyDownHandler {
private SimpleDayCell selectionEnd;
private final VCalendar calendar;
private boolean rangeSelectDisabled;
- private boolean disabled;
private boolean enabled = true;
private final HandlerRegistration keyDownHandler;
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
index cf8006ef66..00fc1ef3ea 100644
--- a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
@@ -83,7 +83,6 @@ public class SimpleDayCell extends FocusableFlowPanel implements
private Widget clickedWidget;
private HandlerRegistration bottomSpacerMouseDownHandler;
private boolean scrollable = false;
- private boolean eventCanceled;
private MonthGrid monthGrid;
private HandlerRegistration keyDownHandler;
diff --git a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
index f91ff9e2b9..8dec26cf90 100644
--- a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
+++ b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
@@ -37,6 +37,10 @@ import com.vaadin.ui.ComboBox;
public class ComboBoxConnector extends AbstractFieldConnector implements
Paintable, SimpleManagedLayout {
+ // oldSuggestionTextMatchTheOldSelection is used to detect when it's safe to
+ // update textbox text by a changed item caption.
+ private boolean oldSuggestionTextMatchTheOldSelection;
+
/*
* (non-Javadoc)
*
@@ -117,7 +121,10 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
boolean suggestionsChanged = !getWidget().initDone
|| !newSuggestions.equals(getWidget().currentSuggestions);
+ oldSuggestionTextMatchTheOldSelection = false;
+
if (suggestionsChanged) {
+ oldSuggestionTextMatchTheOldSelection = isWidgetsCurrentSelectionTextInTextBox();
getWidget().currentSuggestions.clear();
if (!getWidget().waitingForFilteringResponse) {
@@ -212,29 +219,38 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
// some item selected
for (FilterSelectSuggestion suggestion : getWidget().currentSuggestions) {
String suggestionKey = suggestion.getOptionKey();
- if (suggestionKey.equals(selectedKey)) {
- if (!getWidget().waitingForFilteringResponse
- || getWidget().popupOpenerClicked) {
- if (!suggestionKey.equals(getWidget().selectedOptionKey)
- || suggestion.getReplacementString().equals(
- getWidget().tb.getText())) {
- // Update text field if we've got a new
- // selection
- // Also update if we've got the same text to
- // retain old text selection behavior
- getWidget().setPromptingOff(
- suggestion.getReplacementString());
- getWidget().selectedOptionKey = suggestionKey;
- }
+ if (!suggestionKey.equals(selectedKey)) {
+ continue;
+ }
+ if (!getWidget().waitingForFilteringResponse
+ || getWidget().popupOpenerClicked) {
+ if (!suggestionKey.equals(getWidget().selectedOptionKey)
+ || suggestion.getReplacementString().equals(
+ getWidget().tb.getText())
+ || oldSuggestionTextMatchTheOldSelection) {
+ // Update text field if we've got a new
+ // selection
+ // Also update if we've got the same text to
+ // retain old text selection behavior
+ // OR if selected item caption is changed.
+ getWidget().setPromptingOff(
+ suggestion.getReplacementString());
+ getWidget().selectedOptionKey = suggestionKey;
}
- getWidget().currentSuggestion = suggestion;
- getWidget().setSelectedItemIcon(suggestion.getIconUri());
- // only a single item can be selected
- break;
}
+ getWidget().currentSuggestion = suggestion;
+ getWidget().setSelectedItemIcon(suggestion.getIconUri());
+ // only a single item can be selected
+ break;
}
}
+ private boolean isWidgetsCurrentSelectionTextInTextBox() {
+ return getWidget().currentSuggestion != null
+ && getWidget().currentSuggestion.getReplacementString().equals(
+ getWidget().tb.getText());
+ }
+
private void resetSelection() {
if (!getWidget().waitingForFilteringResponse
|| getWidget().popupOpenerClicked) {
diff --git a/client/src/com/vaadin/client/ui/dd/VIsOverId.java b/client/src/com/vaadin/client/ui/dd/VIsOverId.java
index f8083f8b60..7e2f596a20 100644
--- a/client/src/com/vaadin/client/ui/dd/VIsOverId.java
+++ b/client/src/com/vaadin/client/ui/dd/VIsOverId.java
@@ -19,7 +19,6 @@
package com.vaadin.client.ui.dd;
import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
import com.vaadin.client.UIDL;
import com.vaadin.shared.ui.dd.AcceptCriterion;
import com.vaadin.ui.AbstractSelect;
@@ -36,8 +35,6 @@ final public class VIsOverId extends VAcceptCriterion {
.getCurrentDropHandler();
ComponentConnector dropHandlerConnector = currentDropHandler
.getConnector();
- ConnectorMap paintableMap = ConnectorMap.get(currentDropHandler
- .getApplicationConnection());
String pid2 = dropHandlerConnector.getConnectorId();
if (pid2.equals(pid)) {
diff --git a/client/src/com/vaadin/client/ui/dd/VItemIdIs.java b/client/src/com/vaadin/client/ui/dd/VItemIdIs.java
index 7d60eda4f9..b022f434f4 100644
--- a/client/src/com/vaadin/client/ui/dd/VItemIdIs.java
+++ b/client/src/com/vaadin/client/ui/dd/VItemIdIs.java
@@ -32,8 +32,6 @@ final public class VItemIdIs extends VAcceptCriterion {
String pid = configuration.getStringAttribute("s");
ComponentConnector dragSource = drag.getTransferable()
.getDragSource();
- VDropHandler currentDropHandler = VDragAndDropManager.get()
- .getCurrentDropHandler();
String pid2 = dragSource.getConnectorId();
if (pid2.equals(pid)) {
Object searchedId = drag.getTransferable().getData("itemId");
diff --git a/client/src/com/vaadin/client/ui/label/LabelConnector.java b/client/src/com/vaadin/client/ui/label/LabelConnector.java
index 9639987e8d..6a04c91562 100644
--- a/client/src/com/vaadin/client/ui/label/LabelConnector.java
+++ b/client/src/com/vaadin/client/ui/label/LabelConnector.java
@@ -36,12 +36,6 @@ public class LabelConnector extends AbstractComponentConnector {
}
@Override
- protected void init() {
- super.init();
- getWidget().setConnection(getConnection());
- }
-
- @Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
boolean sinkOnloads = false;
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
index 49b3661431..37a97f3399 100644
--- a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
+++ b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
@@ -19,9 +19,11 @@ package com.vaadin.client.ui.orderedlayout;
import java.util.List;
import com.google.gwt.aria.client.Roles;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
@@ -456,6 +458,9 @@ public final class Slot extends SimplePanel {
// Caption wrappers
Widget widget = getWidget();
+ final Element focusedElement = Util.getFocusedElement();
+ // By default focus will not be lost
+ boolean focusLost = false;
if (captionText != null || iconUrl != null || error != null || required) {
if (caption == null) {
caption = DOM.createDiv();
@@ -466,6 +471,10 @@ public final class Slot extends SimplePanel {
orphan(widget);
captionWrap.appendChild(widget.getElement());
adopt(widget);
+
+ // Made changes to DOM. Focus can be lost if it was in the
+ // widget.
+ focusLost = widget.getElement().isOrHasChild(focusedElement);
}
} else if (caption != null) {
orphan(widget);
@@ -474,6 +483,9 @@ public final class Slot extends SimplePanel {
captionWrap.removeFromParent();
caption = null;
captionWrap = null;
+
+ // Made changes to DOM. Focus can be lost if it was in the widget.
+ focusLost = widget.getElement().isOrHasChild(focusedElement);
}
// Caption text
@@ -560,6 +572,45 @@ public final class Slot extends SimplePanel {
setCaptionPosition(CaptionPosition.RIGHT);
}
}
+
+ if (focusLost) {
+ // Find out what element is currently focused.
+ Element currentFocus = Util.getFocusedElement();
+ if (currentFocus != null
+ && currentFocus.equals(Document.get().getBody())) {
+ // Focus has moved to BodyElement and should be moved back to
+ // original location. This happened because of adding or
+ // removing the captionWrap
+ focusedElement.focus();
+ } else if (currentFocus != focusedElement) {
+ // Focus is either moved somewhere else on purpose or IE has
+ // lost it. Investigate further.
+ Timer focusTimer = new Timer() {
+
+ @Override
+ public void run() {
+ if (Util.getFocusedElement() == null) {
+ // This should never become an infinite loop and
+ // even if it does it will be stopped once something
+ // is done with the browser.
+ schedule(25);
+ } else if (Util.getFocusedElement().equals(
+ Document.get().getBody())) {
+ // Focus found it's way to BodyElement. Now it can
+ // be restored
+ focusedElement.focus();
+ }
+ }
+ };
+ if (BrowserInfo.get().isIE8()) {
+ // IE8 can't fix the focus immediately. It will fail.
+ focusTimer.schedule(25);
+ } else {
+ // Newer IE versions can handle things immediately.
+ focusTimer.run();
+ }
+ }
+ }
}
/**
diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java
index eacd5bc77a..d2c700ab06 100644
--- a/client/src/com/vaadin/client/ui/table/TableConnector.java
+++ b/client/src/com/vaadin/client/ui/table/TableConnector.java
@@ -142,9 +142,6 @@ public class TableConnector extends AbstractHasComponentsConnector implements
getWidget().updateSortingProperties(uidl);
- boolean keyboardSelectionOverRowFetchInProgress = getWidget()
- .selectSelectedRows(uidl);
-
getWidget().updateActionMap(uidl);
getWidget().updateColumnProperties(uidl);
@@ -216,6 +213,9 @@ public class TableConnector extends AbstractHasComponentsConnector implements
}
}
+ boolean keyboardSelectionOverRowFetchInProgress = getWidget()
+ .selectSelectedRows(uidl);
+
// If a row had an open context menu before the update, and after the
// update there's a row with the same key as that row, restore the
// context menu. See #8526.
diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
index 6f89137918..61207ffa53 100644
--- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java
+++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
@@ -18,6 +18,7 @@ package com.vaadin.client.ui.tree;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Element;
@@ -141,9 +142,24 @@ public class TreeConnector extends AbstractComponentConnector implements
getWidget().lastSelection = getWidget().getNodeByKey(
getWidget().lastSelection.key);
}
+
if (getWidget().focusedNode != null) {
- getWidget().setFocusedNode(
- getWidget().getNodeByKey(getWidget().focusedNode.key));
+
+ Set<String> selectedIds = getWidget().selectedIds;
+
+ // If the focused node is not between the selected nodes, we need to
+ // refresh the focused node to prevent an undesired scroll. #12618.
+ if (!selectedIds.isEmpty()
+ && !selectedIds.contains(getWidget().focusedNode.key)) {
+ String keySelectedId = selectedIds.iterator().next();
+
+ TreeNode nodeToSelect = getWidget().getNodeByKey(keySelectedId);
+
+ getWidget().setFocusedNode(nodeToSelect);
+ } else {
+ getWidget().setFocusedNode(
+ getWidget().getNodeByKey(getWidget().focusedNode.key));
+ }
}
if (getWidget().lastSelection == null
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index d0f3c8603f..17a23baad5 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -49,7 +49,6 @@ import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
-import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.Paintable;
import com.vaadin.client.ServerConnector;
@@ -192,7 +191,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
@Override
public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
- ConnectorMap paintableMap = ConnectorMap.get(getConnection());
getWidget().id = getConnectorId();
boolean firstPaint = getWidget().connection == null;
getWidget().connection = client;
diff --git a/client/src/com/vaadin/client/ui/upload/UploadConnector.java b/client/src/com/vaadin/client/ui/upload/UploadConnector.java
index 937ff438ac..989a913adc 100644
--- a/client/src/com/vaadin/client/ui/upload/UploadConnector.java
+++ b/client/src/com/vaadin/client/ui/upload/UploadConnector.java
@@ -22,12 +22,22 @@ import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.VUpload;
import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.upload.UploadClientRpc;
import com.vaadin.ui.Upload;
@Connect(Upload.class)
public class UploadConnector extends AbstractComponentConnector implements
Paintable {
+ public UploadConnector() {
+ registerRpc(UploadClientRpc.class, new UploadClientRpc() {
+ @Override
+ public void submitUpload() {
+ getWidget().submit();
+ }
+ });
+ }
+
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if (!isRealUpdate(uidl)) {
@@ -37,10 +47,6 @@ public class UploadConnector extends AbstractComponentConnector implements
getWidget().t.schedule(400);
return;
}
- if (uidl.hasAttribute("forceSubmit")) {
- getWidget().submit();
- return;
- }
getWidget().setImmediate(getState().immediate);
getWidget().client = client;
getWidget().paintableId = uidl.getId();