Refactored the ComponentLocator class to allow for implementing different locator strategies. Change-Id: I93f3decbce4d4361cc605bcf0ce4379a187c482ctags/7.2.0.beta1
import com.vaadin.client.communication.PushConnection; | import com.vaadin.client.communication.PushConnection; | ||||
import com.vaadin.client.communication.RpcManager; | import com.vaadin.client.communication.RpcManager; | ||||
import com.vaadin.client.communication.StateChangeEvent; | import com.vaadin.client.communication.StateChangeEvent; | ||||
import com.vaadin.client.componentlocator.ComponentLocator; | |||||
import com.vaadin.client.extensions.AbstractExtensionConnector; | import com.vaadin.client.extensions.AbstractExtensionConnector; | ||||
import com.vaadin.client.metadata.ConnectorBundleLoader; | import com.vaadin.client.metadata.ConnectorBundleLoader; | ||||
import com.vaadin.client.metadata.Method; | import com.vaadin.client.metadata.Method; | ||||
private native void initializeTestbenchHooks( | private native void initializeTestbenchHooks( | ||||
ComponentLocator componentLocator, String TTAppId) | 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 | 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.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() | private static native final int calculateBootstrapTime() |
*/ | */ | ||||
package com.vaadin.client; | 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 | * ComponentLocator provides methods for generating a String locator for a given | ||||
* DOM element and for locating a DOM element using a String locator. | * 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. | * 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) { | 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; | |||||
} | |||||
} | } |
/* | |||||
* 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 com.google.gwt.user.client.Element; | |||||
import com.vaadin.client.ApplicationConnection; | |||||
import com.vaadin.client.BrowserInfo; | |||||
/** | |||||
* 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 LegacyLocatorStrategy legacyLocatorStrategy = new LegacyLocatorStrategy( | |||||
this); | |||||
/** | |||||
* Reference to ApplicationConnection instance. | |||||
*/ | |||||
private ApplicationConnection client; | |||||
/** | |||||
* Construct a ComponentLocator for the given ApplicationConnection. | |||||
* | |||||
* @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 #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) { | |||||
return legacyLocatorStrategy | |||||
.getPathForElement(getElement(targetElement)); | |||||
} | |||||
/** | |||||
* 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); | |||||
} | |||||
/** | |||||
* 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) { | |||||
return legacyLocatorStrategy.getElementByPath(path); | |||||
} | |||||
/** | |||||
* @return the application connection | |||||
*/ | |||||
ApplicationConnection getClient() { | |||||
return client; | |||||
} | |||||
} |
/* | |||||
* 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.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.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 { | |||||
private final ComponentLocator componentLocator; | |||||
/** | |||||
* 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"; | |||||
public LegacyLocatorStrategy(ComponentLocator componentLocator) { | |||||
this.componentLocator = componentLocator; | |||||
} | |||||
@Override | |||||
public String getPathForElement(Element targetElement) { | |||||
ComponentConnector connector = Util.findPaintable( | |||||
componentLocator.getClient(), 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; | |||||
} | |||||
} | |||||
@Override | |||||
public Element getElementByPath(String path) { | |||||
/* | |||||
* Path is of type "targetWidgetPath#componentPart" or | |||||
* "targetWidgetPath". | |||||
*/ | |||||
String parts[] = path.split(LegacyLocatorStrategy.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; | |||||
} | |||||
/** | |||||
* 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 (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; | |||||
} | |||||
/** | |||||
* 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)}. | |||||
* <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( | |||||
componentLocator.getClient()).getConnector(w); | |||||
List<WindowConnector> subWindowList = componentLocator.getClient() | |||||
.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 = componentLocator.getClient().getUIConnector().getWidget(); | |||||
} else if (w == null) { | |||||
String id = part; | |||||
// Must be old static pid (PID_S*) | |||||
ServerConnector connector = ConnectorMap.get( | |||||
componentLocator.getClient()).getConnector(id); | |||||
if (connector == null) { | |||||
// Lookup by component id | |||||
// TODO Optimize this | |||||
connector = findConnectorById(componentLocator.getClient() | |||||
.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 = componentLocator | |||||
.getClient().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 componentLocator.getClient().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; | |||||
} | |||||
} |
/* | |||||
* 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 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 { | |||||
String getPathForElement(Element targetElement); | |||||
Element getElementByPath(String path); | |||||
} |
import com.vaadin.client.ApplicationConfiguration; | import com.vaadin.client.ApplicationConfiguration; | ||||
import com.vaadin.client.ApplicationConnection; | import com.vaadin.client.ApplicationConnection; | ||||
import com.vaadin.client.ComponentConnector; | import com.vaadin.client.ComponentConnector; | ||||
import com.vaadin.client.ComponentLocator; | |||||
import com.vaadin.client.ServerConnector; | import com.vaadin.client.ServerConnector; | ||||
import com.vaadin.client.Util; | import com.vaadin.client.Util; | ||||
import com.vaadin.client.ValueMap; | import com.vaadin.client.ValueMap; | ||||
import com.vaadin.client.componentlocator.ComponentLocator; | |||||
/** | /** | ||||
* Provides functionality for picking selectors for Vaadin TestBench. | * Provides functionality for picking selectors for Vaadin TestBench. |
import com.google.gwt.user.client.Element; | import com.google.gwt.user.client.Element; | ||||
import com.google.gwt.user.client.ui.Widget; | 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 | * Interface implemented by {@link Widget}s which can provide identifiers for at | ||||
*/ | */ | ||||
String getSubPartName(Element subElement); | String getSubPartName(Element subElement); | ||||
} | |||||
} |