aboutsummaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java123
-rw-r--r--client/src/com/vaadin/client/LayoutManager.java40
-rw-r--r--client/src/com/vaadin/client/VCaption.java14
-rw-r--r--client/src/com/vaadin/client/VErrorMessage.java3
-rw-r--r--client/src/com/vaadin/client/VTooltip.java68
-rw-r--r--client/src/com/vaadin/client/communication/AtmospherePushConnection.java42
-rw-r--r--client/src/com/vaadin/client/communication/PushConnection.java7
-rw-r--r--client/src/com/vaadin/client/ui/Icon.java13
-rw-r--r--client/src/com/vaadin/client/ui/VFilterSelect.java5
-rw-r--r--client/src/com/vaadin/client/ui/VNotification.java77
-rw-r--r--client/src/com/vaadin/client/ui/VOverlay.java2
-rw-r--r--client/src/com/vaadin/client/ui/VTabsheet.java191
-rw-r--r--client/src/com/vaadin/client/ui/VTabsheetBase.java2
-rw-r--r--client/src/com/vaadin/client/ui/VTree.java17
-rw-r--r--client/src/com/vaadin/client/ui/VUI.java36
-rw-r--r--client/src/com/vaadin/client/ui/VWindow.java357
-rw-r--r--client/src/com/vaadin/client/ui/VWindowOverlay.java77
-rw-r--r--client/src/com/vaadin/client/ui/button/ButtonConnector.java3
-rw-r--r--client/src/com/vaadin/client/ui/dd/VHtml5File.java8
-rw-r--r--client/src/com/vaadin/client/ui/tree/TreeConnector.java5
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java9
-rw-r--r--client/src/com/vaadin/client/ui/window/WindowConnector.java10
22 files changed, 959 insertions, 150 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index 0d9c859ee8..cd1f8a206d 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -49,6 +49,7 @@ import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.regexp.shared.MatchResult;
@@ -90,6 +91,7 @@ import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.Version;
import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
import com.vaadin.shared.communication.MethodInvocation;
@@ -136,10 +138,6 @@ public class ApplicationConnection {
public static final String ERROR_CLASSNAME_EXT = "-error";
- public static final char VAR_BURST_SEPARATOR = '\u001d';
-
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
-
/**
* A string that, if found in a non-JSON response to a UIDL request, will
* cause the browser to refresh the page. If followed by a colon, optional
@@ -270,8 +268,6 @@ public class ApplicationConnection {
/** Event bus for communication events */
private EventBus eventBus = GWT.create(SimpleEventBus.class);
- private int lastResponseId = -1;
-
/**
* The communication handler methods are called at certain points during
* communication with the server. This allows for making add-ons that keep
@@ -490,6 +486,10 @@ public class ApplicationConnection {
// initial UIDL provided in DOM, continue as if returned by request
handleJSONText(jsonText, -1);
+
+ // Tooltip can't be created earlier because the necessary fields are
+ // not setup to add it in the correct place in the DOM
+ getVTooltip().showAssistive(new TooltipInfo(" "));
}
}
@@ -671,7 +671,7 @@ public class ApplicationConnection {
}-*/;
protected void repaintAll() {
- makeUidlRequest("", getRepaintAllParameters());
+ makeUidlRequest(new JSONArray(), getRepaintAllParameters());
}
/**
@@ -702,20 +702,25 @@ public class ApplicationConnection {
/**
* Makes an UIDL request to the server.
*
- * @param requestData
- * Data that is passed to the server.
+ * @param reqInvocations
+ * Data containing RPC invocations and all related information.
* @param extraParams
* Parameters that are added as GET parameters to the url.
* Contains key=value pairs joined by & characters or is empty if
* no parameters should be added. Should not start with any
* special character.
*/
- protected void makeUidlRequest(final String requestData,
+ protected void makeUidlRequest(final JSONArray reqInvocations,
final String extraParams) {
startRequest();
- // Security: double cookie submission pattern
- final String payload = getCsrfToken() + VAR_BURST_SEPARATOR
- + requestData;
+
+ JSONObject payload = new JSONObject();
+ payload.put(ApplicationConstants.CSRF_TOKEN, new JSONString(
+ getCsrfToken()));
+ payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
+ payload.put(ApplicationConstants.SERVER_SYNC_ID, new JSONNumber(
+ lastSeenServerSyncId));
+
VConsole.log("Making UIDL Request with params: " + payload);
String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ ApplicationConstants.UIDL_PATH + '/');
@@ -739,7 +744,7 @@ public class ApplicationConnection {
* @param payload
* The contents of the request to send
*/
- protected void doUidlRequest(final String uri, final String payload) {
+ protected void doUidlRequest(final String uri, final JSONObject payload) {
RequestCallback requestCallback = new RequestCallback() {
@Override
public void onError(Request request, Throwable exception) {
@@ -902,14 +907,14 @@ public class ApplicationConnection {
* @throws RequestException
* if the request could not be sent
*/
- protected void doAjaxRequest(String uri, String payload,
+ protected void doAjaxRequest(String uri, JSONObject payload,
RequestCallback requestCallback) throws RequestException {
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
// TODO enable timeout
// rb.setTimeoutMillis(timeoutMillis);
// TODO this should be configurable
- rb.setHeader("Content-Type", "text/plain;charset=utf-8");
- rb.setRequestData(payload);
+ rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE);
+ rb.setRequestData(payload.toString());
rb.setCallback(requestCallback);
final Request request = rb.send();
@@ -974,6 +979,29 @@ public class ApplicationConnection {
*/
private ValueMap serverTimingInfo;
+ /**
+ * Holds the last seen response id given by the server.
+ * <p>
+ * The server generates a strictly increasing id for each response to each
+ * request from the client. This ID is then replayed back to the server on
+ * each request. This helps the server in knowing in what state the client
+ * is, and compare it to its own state. In short, it helps with concurrent
+ * changes between the client and server.
+ * <p>
+ * Initial value, i.e. no responses received from the server, is
+ * {@link #UNDEFINED_SYNC_ID} ({@value #UNDEFINED_SYNC_ID}). This happens
+ * between the bootstrap HTML being loaded and the first UI being rendered;
+ */
+ private int lastSeenServerSyncId = UNDEFINED_SYNC_ID;
+
+ /**
+ * The value of an undefined sync id.
+ * <p>
+ * This must be <code>-1</code>, because of the contract in
+ * {@link #getLastResponseId()}
+ */
+ private static final int UNDEFINED_SYNC_ID = -1;
+
static final int MAX_CSS_WAITS = 100;
protected void handleWhenCSSLoaded(final String jsonText,
@@ -1254,7 +1282,13 @@ public class ApplicationConnection {
* @return and id identifying the response
*/
public int getLastResponseId() {
- return lastResponseId;
+ /*
+ * The discrepancy between field name and getter name is simply historic
+ * - API can't be changed, but the field was repurposed in a more
+ * general, yet compatible, use. "Response id" was deemed unsuitable a
+ * name, so it was called "server sync id" instead.
+ */
+ return lastSeenServerSyncId;
}
protected void handleUIDLMessage(final Date start, final String jsonText,
@@ -1281,6 +1315,17 @@ public class ApplicationConnection {
VConsole.log("Handling message from server");
eventBus.fireEvent(new ResponseHandlingStartedEvent(this));
+ if (json.containsKey(ApplicationConstants.SERVER_SYNC_ID)) {
+ int syncId = json.getInt(ApplicationConstants.SERVER_SYNC_ID);
+
+ assert (lastSeenServerSyncId == UNDEFINED_SYNC_ID || syncId == lastSeenServerSyncId + 1) : "Newly retrieved server sync id was not exactly one larger than the previous one (new: "
+ + syncId + ", last seen: " + lastSeenServerSyncId + ")";
+
+ lastSeenServerSyncId = syncId;
+ } else {
+ VConsole.error("Server response didn't contain an id.");
+ }
+
// Handle redirect
if (json.containsKey("redirect")) {
String url = json.getValueMap("redirect").getString("url");
@@ -1289,8 +1334,6 @@ public class ApplicationConnection {
return;
}
- lastResponseId++;
-
final MultiStepDuration handleUIDLDuration = new MultiStepDuration();
// Get security key
@@ -2464,15 +2507,13 @@ public class ApplicationConnection {
*/
private void buildAndSendVariableBurst(
LinkedHashMap<String, MethodInvocation> pendingInvocations) {
- final StringBuffer req = new StringBuffer();
- while (!pendingInvocations.isEmpty()) {
+ JSONArray reqJson = new JSONArray();
+ if (!pendingInvocations.isEmpty()) {
if (ApplicationConfiguration.isDebugMode()) {
Util.logVariableBurst(this, pendingInvocations.values());
}
- JSONArray reqJson = new JSONArray();
-
for (MethodInvocation invocation : pendingInvocations.values()) {
JSONArray invocationJson = new JSONArray();
invocationJson.set(0,
@@ -2511,9 +2552,6 @@ public class ApplicationConnection {
reqJson.set(reqJson.size(), invocationJson);
}
- // escape burst separators (if any)
- req.append(escapeBurstContents(reqJson.toString()));
-
pendingInvocations.clear();
// Keep tag string short
lastInvocationTag = 0;
@@ -2537,7 +2575,7 @@ public class ApplicationConnection {
getConfiguration().setWidgetsetVersionSent();
}
- makeUidlRequest(req.toString(), extraParams);
+ makeUidlRequest(reqJson, extraParams);
}
private boolean isJavascriptRpc(MethodInvocation invocation) {
@@ -2781,35 +2819,6 @@ public class ApplicationConnection {
}
/**
- * Encode burst separator characters in a String for transport over the
- * network. This protects from separator injection attacks.
- *
- * @param value
- * to encode
- * @return encoded value
- */
- protected String escapeBurstContents(String value) {
- final StringBuilder result = new StringBuilder();
- for (int i = 0; i < value.length(); ++i) {
- char character = value.charAt(i);
- switch (character) {
- case VAR_ESCAPE_CHARACTER:
- // fall-through - escape character is duplicated
- case VAR_BURST_SEPARATOR:
- result.append(VAR_ESCAPE_CHARACTER);
- // encode as letters for easier reading
- result.append(((char) (character + 0x30)));
- break;
- default:
- // the char is not a special one - add it to the result as is
- result.append(character);
- break;
- }
- }
- return result.toString();
- }
-
- /**
* Does absolutely nothing. Replaced by {@link LayoutManager}.
*
* @param container
diff --git a/client/src/com/vaadin/client/LayoutManager.java b/client/src/com/vaadin/client/LayoutManager.java
index 1ced003146..5d27527793 100644
--- a/client/src/com/vaadin/client/LayoutManager.java
+++ b/client/src/com/vaadin/client/LayoutManager.java
@@ -1449,10 +1449,15 @@ public class LayoutManager {
/**
* Informs this LayoutManager that the size of a component might have
- * changed. If there is no upcoming layout phase, a new layout phase is
- * scheduled. This method should be used whenever a size might have changed
- * from outside of Vaadin's normal update phase, e.g. when an icon has been
- * loaded or when the user resizes some part of the UI using the mouse.
+ * changed. This method should be used whenever the size of an individual
+ * component might have changed from outside of Vaadin's normal update
+ * phase, e.g. when an icon has been loaded or when the user resizes some
+ * part of the UI using the mouse.
+ * <p>
+ * To set an entire component hierarchy to be measured, use
+ * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead.
+ * <p>
+ * If there is no upcoming layout phase, a new layout phase is scheduled.
*
* @param component
* the component whose size might have changed.
@@ -1466,6 +1471,33 @@ public class LayoutManager {
}
}
+ /**
+ * Informs this LayoutManager that some sizes in a component hierarchy might
+ * have changed. This method should be used whenever the size of any child
+ * component might have changed from outside of Vaadin's normal update
+ * phase, e.g. when a CSS class name related to sizing has been changed.
+ * <p>
+ * To set a single component to be measured, use
+ * {@link #setNeedsMeasure(ComponentConnector)} instead.
+ * <p>
+ * If there is no upcoming layout phase, a new layout phase is scheduled.
+ *
+ * @since 7.2
+ * @param component
+ * the component at the root of the component hierarchy to
+ * measure
+ */
+ public void setNeedsMeasureRecursively(ComponentConnector component) {
+ setNeedsMeasure(component);
+
+ if (component instanceof HasComponentsConnector) {
+ HasComponentsConnector hasComponents = (HasComponentsConnector) component;
+ for (ComponentConnector child : hasComponents.getChildComponents()) {
+ setNeedsMeasureRecursively(child);
+ }
+ }
+ }
+
public void setEverythingNeedsMeasure() {
everythingNeedsMeasure = true;
}
diff --git a/client/src/com/vaadin/client/VCaption.java b/client/src/com/vaadin/client/VCaption.java
index d0338de4a1..d96e6ed653 100644
--- a/client/src/com/vaadin/client/VCaption.java
+++ b/client/src/com/vaadin/client/VCaption.java
@@ -42,6 +42,8 @@ public class VCaption extends HTML {
private Icon icon;
+ private String iconAltText = "";
+
private Element captionText;
private final ApplicationConnection client;
@@ -112,6 +114,8 @@ public class VCaption extends HTML {
if (null != owner) {
AriaHelper.bindCaption(owner.getWidget(), null);
+ AriaHelper.handleInputInvalid(owner.getWidget(), false);
+ AriaHelper.handleInputRequired(owner.getWidget(), false);
}
}
@@ -300,6 +304,14 @@ public class VCaption extends HTML {
@Deprecated
public boolean updateCaptionWithoutOwner(String caption, boolean disabled,
boolean hasDescription, boolean hasError, String iconURL) {
+ return updateCaptionWithoutOwner(caption, disabled, hasDescription,
+ hasError, iconURL, "");
+ }
+
+ @Deprecated
+ public boolean updateCaptionWithoutOwner(String caption, boolean disabled,
+ boolean hasDescription, boolean hasError, String iconURL,
+ String iconAltText) {
boolean wasPlacedAfterComponent = placedAfterComponent;
// Caption is placed after component unless there is some part which
@@ -332,7 +344,7 @@ public class VCaption extends HTML {
// Icon forces the caption to be above the component
placedAfterComponent = false;
- icon.setUri(iconURL);
+ icon.setUri(iconURL, iconAltText);
} else if (icon != null) {
// Remove existing
diff --git a/client/src/com/vaadin/client/VErrorMessage.java b/client/src/com/vaadin/client/VErrorMessage.java
index 2e42b98a05..a384b451dd 100644
--- a/client/src/com/vaadin/client/VErrorMessage.java
+++ b/client/src/com/vaadin/client/VErrorMessage.java
@@ -31,9 +31,6 @@ public class VErrorMessage extends FlowPanel {
public VErrorMessage() {
super();
setStyleName(CLASSNAME);
-
- // Needed for binding with WAI-ARIA attributes
- getElement().setId(DOM.createUniqueId());
}
/**
diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java
index 6191821988..e687712b9c 100644
--- a/client/src/com/vaadin/client/VTooltip.java
+++ b/client/src/com/vaadin/client/VTooltip.java
@@ -15,7 +15,8 @@
*/
package com.vaadin.client;
-import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.LiveValue;
+import com.google.gwt.aria.client.RelevantValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
@@ -36,12 +37,12 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ui.VOverlay;
+import com.vaadin.client.ui.VWindowOverlay;
/**
* TODO open for extension
*/
-public class VTooltip extends VOverlay {
+public class VTooltip extends VWindowOverlay {
private static final String CLASSNAME = "v-tooltip";
private static final int MARGIN = 4;
public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
@@ -57,7 +58,6 @@ public class VTooltip extends VOverlay {
private boolean justClosed = false;
private String uniqueId = DOM.createUniqueId();
- private Element layoutElement;
private int maxWidth;
// Delays for the tooltip, configurable on the server side
@@ -80,17 +80,28 @@ public class VTooltip extends VOverlay {
setWidget(layout);
layout.add(em);
DOM.setElementProperty(description, "className", CLASSNAME + "-text");
-
- layoutElement = layout.getElement();
- DOM.appendChild(layoutElement, description);
+ DOM.appendChild(layout.getElement(), description);
setSinkShadowEvents(true);
- // Used to bind the tooltip to the owner for assistive devices
- layoutElement.setId(uniqueId);
+ // When a tooltip is shown, the content of the tooltip changes. With a
+ // tooltip being a live-area, this change is notified to a assistive
+ // device.
+ Roles.getTooltipRole().set(getElement());
+ Roles.getTooltipRole().setAriaLiveProperty(getElement(),
+ LiveValue.ASSERTIVE);
+ Roles.getTooltipRole().setAriaRelevantProperty(getElement(),
+ RelevantValue.ADDITIONS);
+ }
- description.setId(DOM.createUniqueId());
- Roles.getTooltipRole().set(layoutElement);
- Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
+ /**
+ * Show the tooltip with the provided info for assistive devices.
+ *
+ * @param info
+ * with the content of the tooltip
+ */
+ public void showAssistive(TooltipInfo info) {
+ updatePosition(null, true);
+ show(info);
}
/**
@@ -229,10 +240,11 @@ public class VTooltip extends VOverlay {
@Override
public void hide() {
- super.hide();
- Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
- Roles.getTooltipRole().removeAriaDescribedbyProperty(
- tooltipEventHandler.currentElement);
+ em.updateMessage("");
+ description.setInnerHTML("");
+
+ updatePosition(null, true);
+ setPopupPosition(tooltipEventMouseX, tooltipEventMouseY);
}
private int tooltipEventMouseX;
@@ -287,9 +299,9 @@ public class VTooltip extends VOverlay {
private com.google.gwt.dom.client.Element currentElement = null;
/**
- * Current element focused
+ * Marker for handling of tooltip through focus
*/
- private boolean currentIsFocused;
+ private boolean handledByFocus;
/**
* Current tooltip active
@@ -392,6 +404,7 @@ public class VTooltip extends VOverlay {
*/
@Override
public void onBlur(BlurEvent be) {
+ handledByFocus = false;
handleHideEvent();
}
@@ -401,7 +414,7 @@ public class VTooltip extends VOverlay {
.getEventTarget());
// We can ignore move event if it's handled by move or over already
- if (currentElement == element && currentIsFocused == isFocused) {
+ if (currentElement == element && handledByFocus == true) {
return;
}
@@ -409,8 +422,6 @@ public class VTooltip extends VOverlay {
if (!connectorAndTooltipFound) {
if (isShowing()) {
handleHideEvent();
- Roles.getButtonRole()
- .removeAriaDescribedbyProperty(element);
} else {
currentTooltipInfo = null;
}
@@ -419,17 +430,12 @@ public class VTooltip extends VOverlay {
if (isShowing()) {
replaceCurrentTooltip();
- Roles.getTooltipRole().removeAriaDescribedbyProperty(
- currentElement);
} else {
showTooltip();
}
-
- Roles.getTooltipRole().setAriaDescribedbyProperty(element,
- Id.of(uniqueId));
}
- currentIsFocused = isFocused;
+ handledByFocus = isFocused;
currentElement = element;
}
}
@@ -464,9 +470,11 @@ public class VTooltip extends VOverlay {
@Override
public void setPopupPositionAndShow(PositionCallback callback) {
- super.setPopupPositionAndShow(callback);
-
- Roles.getTooltipRole().setAriaHiddenState(layoutElement, false);
+ if (isAttached()) {
+ callback.setPosition(getOffsetWidth(), getOffsetHeight());
+ } else {
+ super.setPopupPositionAndShow(callback);
+ }
}
/**
diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
index 3cecb09dc1..ccb01c5a30 100644
--- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
+++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Command;
import com.vaadin.client.ApplicationConfiguration;
import com.vaadin.client.ApplicationConnection;
@@ -110,7 +111,7 @@ public class AtmospherePushConnection implements PushConnection {
private JavaScriptObject socket;
- private ArrayList<String> messageQueue = new ArrayList<String>();
+ private ArrayList<JSONObject> messageQueue = new ArrayList<JSONObject>();
private State state = State.CONNECT_PENDING;
@@ -191,14 +192,8 @@ public class AtmospherePushConnection implements PushConnection {
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * com.vaadin.client.communication.PushConenction#push(java.lang.String)
- */
@Override
- public void push(String message) {
+ public void push(JSONObject message) {
switch (state) {
case CONNECT_PENDING:
assert isActive();
@@ -210,12 +205,13 @@ public class AtmospherePushConnection implements PushConnection {
VConsole.log("Sending push message: " + message);
if (transport.equals("websocket")) {
- FragmentedMessage fragmented = new FragmentedMessage(message);
+ FragmentedMessage fragmented = new FragmentedMessage(
+ message.toString());
while (fragmented.hasNextFragment()) {
doPush(socket, fragmented.getNextFragment());
}
} else {
- doPush(socket, message);
+ doPush(socket, message.toString());
}
break;
case DISCONNECT_PENDING:
@@ -228,15 +224,31 @@ public class AtmospherePushConnection implements PushConnection {
return config;
}
- protected void onOpen(AtmosphereResponse response) {
- transport = response.getTransport();
+ protected void onReopen(AtmosphereResponse response) {
+ VConsole.log("Push connection re-established using " + transport);
+ onConnect(response);
+ }
+ protected void onOpen(AtmosphereResponse response) {
VConsole.log("Push connection established using " + transport);
+ onConnect(response);
+ }
+
+ /**
+ * Called whenever a server push connection is established (or
+ * re-established).
+ *
+ * @param response
+ *
+ * @since 7.2
+ */
+ protected void onConnect(AtmosphereResponse response) {
+ transport = response.getTransport();
switch (state) {
case CONNECT_PENDING:
state = State.CONNECTED;
- for (String message : messageQueue) {
+ for (JSONObject message : messageQueue) {
push(message);
}
messageQueue.clear();
@@ -422,6 +434,7 @@ public class AtmospherePushConnection implements PushConnection {
reconnectInterval: 5000,
maxReconnectOnClose: 10000000,
trackMessageLength: true,
+ enableProtocol: false,
messageDelimiter: String.fromCharCode(@com.vaadin.shared.communication.PushConstants::MESSAGE_DELIMITER)
};
}-*/;
@@ -435,6 +448,9 @@ public class AtmospherePushConnection implements PushConnection {
config.onOpen = $entry(function(response) {
self.@com.vaadin.client.communication.AtmospherePushConnection::onOpen(*)(response);
});
+ config.onReopen = $entry(function(response) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onReopen(*)(response);
+ });
config.onMessage = $entry(function(response) {
self.@com.vaadin.client.communication.AtmospherePushConnection::onMessage(*)(response);
});
diff --git a/client/src/com/vaadin/client/communication/PushConnection.java b/client/src/com/vaadin/client/communication/PushConnection.java
index a7eba224be..ba79af9d2c 100644
--- a/client/src/com/vaadin/client/communication/PushConnection.java
+++ b/client/src/com/vaadin/client/communication/PushConnection.java
@@ -16,6 +16,7 @@
package com.vaadin.client.communication;
+import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Command;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler;
@@ -53,14 +54,14 @@ public interface PushConnection {
* replay those messages in the original order when the connection has been
* established.
*
- * @param message
- * the message to push
+ * @param payload
+ * the payload to push
* @throws IllegalStateException
* if this connection is not active
*
* @see #isActive()
*/
- public void push(String message);
+ public void push(JSONObject payload);
/**
* Checks whether this push connection is in a state where it can push
diff --git a/client/src/com/vaadin/client/ui/Icon.java b/client/src/com/vaadin/client/ui/Icon.java
index 9d5829c079..4a0e858798 100644
--- a/client/src/com/vaadin/client/ui/Icon.java
+++ b/client/src/com/vaadin/client/ui/Icon.java
@@ -34,11 +34,19 @@ public class Icon extends UIObject {
}
public Icon(ApplicationConnection client, String uidlUri) {
+ this(client, uidlUri, "");
+ }
+
+ public Icon(ApplicationConnection client, String uidlUri, String iconAltText) {
this(client);
- setUri(uidlUri);
+ setUri(uidlUri, iconAltText);
}
public void setUri(String uidlUri) {
+ setUri(uidlUri, "");
+ }
+
+ public void setUri(String uidlUri, String uidlAlt) {
if (!uidlUri.equals(myUri)) {
/*
* Start sinking onload events, widgets responsibility to react. We
@@ -51,6 +59,8 @@ public class Icon extends UIObject {
DOM.setElementProperty(getElement(), "src", uri);
myUri = uidlUri;
}
+
+ setAlternateText(uidlAlt);
}
/**
@@ -63,5 +73,4 @@ public class Icon extends UIObject {
getElement().setAttribute("alt",
alternateText == null ? "" : alternateText);
}
-
}
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
index 9b14fbbb37..7efb5b8867 100644
--- a/client/src/com/vaadin/client/ui/VFilterSelect.java
+++ b/client/src/com/vaadin/client/ui/VFilterSelect.java
@@ -1786,6 +1786,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
if (client.hasEventListeners(this, EventId.FOCUS)) {
client.updateVariable(paintableId, EventId.FOCUS, "", true);
}
+
+ ComponentConnector connector = ConnectorMap.get(client).getConnector(
+ this);
+ client.getVTooltip().showAssistive(
+ connector.getTooltipInfo(getElement()));
}
/**
diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java
index 7019394e3b..128de0a285 100644
--- a/client/src/com/vaadin/client/ui/VNotification.java
+++ b/client/src/com/vaadin/client/ui/VNotification.java
@@ -21,19 +21,25 @@ import java.util.Date;
import java.util.EventObject;
import java.util.Iterator;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.KeyCodes;
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.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.Position;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
import com.vaadin.shared.ui.ui.UIConstants;
public class VNotification extends VOverlay {
@@ -46,6 +52,12 @@ public class VNotification extends VOverlay {
public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
+ /**
+ * Position that is only accessible for assistive devices, invisible for
+ * visual users.
+ */
+ public static final Position ASSISTIVE = Position.ASSISTIVE;
+
public static final int DELAY_FOREVER = -1;
public static final int DELAY_NONE = 0;
@@ -149,15 +161,67 @@ public class VNotification extends VOverlay {
}
public void show(Widget widget, Position position, String style) {
- setWidget(widget);
+ NotificationConfigurationBean styleSetup = getUiState(style);
+ setWaiAriaRole(styleSetup);
+
+ FlowPanel panel = new FlowPanel();
+ if (styleSetup.hasAssistivePrefix()) {
+ panel.add(new Label(styleSetup.getAssistivePrefix()));
+ AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
+ true);
+ }
+
+ panel.add(widget);
+
+ if (styleSetup.hasAssistivePostfix()) {
+ panel.add(new Label(styleSetup.getAssistivePostfix()));
+ AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
+ true);
+ }
+ setWidget(panel);
show(position, style);
}
public void show(String html, Position position, String style) {
- setWidget(new HTML(html));
+ NotificationConfigurationBean styleSetup = getUiState(style);
+ String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
+
+ setWaiAriaRole(styleSetup);
+
+ String type = "";
+ String usage = "";
+
+ if (styleSetup != null && styleSetup.hasAssistivePrefix()) {
+ type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
+ + styleSetup.getAssistivePrefix() + "</span>";
+ }
+
+ if (styleSetup != null && styleSetup.hasAssistivePostfix()) {
+ usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
+ + styleSetup.getAssistivePostfix() + "</span>";
+ }
+
+ setWidget(new HTML(type + html + usage));
show(position, style);
}
+ private NotificationConfigurationBean getUiState(String style) {
+ NotificationConfigurationBean styleSetup = getApplicationConnection()
+ .getUIConnector().getState().notificationConfiguration.setup
+ .get(style);
+ return styleSetup;
+ }
+
+ private void setWaiAriaRole(NotificationConfigurationBean styleSetup) {
+ Roles.getAlertRole().set(getElement());
+
+ if (styleSetup != null && styleSetup.getAssistiveRole() != null) {
+ if (Role.STATUS == styleSetup.getAssistiveRole()) {
+ Roles.getStatusRole().set(getElement());
+ }
+ }
+ }
+
public void show(Position position, String style) {
setOpacity(getElement(), startOpacity);
if (style != null) {
@@ -257,6 +321,10 @@ public class VNotification extends VOverlay {
DOM.setStyleAttribute(el, "top", "");
DOM.setStyleAttribute(el, "bottom", "0px");
break;
+ case ASSISTIVE:
+ DOM.setStyleAttribute(el, "top", "-2000px");
+ DOM.setStyleAttribute(el, "left", "-2000px");
+ break;
default:
case MIDDLE_CENTER:
center();
@@ -377,7 +445,8 @@ public class VNotification extends VOverlay {
final String parsedUri = client
.translateVaadinUri(notification
.getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON));
- html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
+ html += "<img src=\"" + Util.escapeAttribute(parsedUri)
+ + "\" alt=\"\" />";
}
if (notification
.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
@@ -417,6 +486,8 @@ public class VNotification extends VOverlay {
public static VNotification createNotification(int delayMsec, Widget owner) {
final VNotification notification = GWT.create(VNotification.class);
+ notification.setWaiAriaRole(null);
+
notification.delayMsec = delayMsec;
if (BrowserInfo.get().isTouchDevice()) {
new Timer() {
diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java
index ced476f9dd..206e26b960 100644
--- a/client/src/com/vaadin/client/ui/VOverlay.java
+++ b/client/src/com/vaadin/client/ui/VOverlay.java
@@ -172,7 +172,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
*
* See default theme 'shadow.css' for implementation example.
*/
- private static final String SHADOW_HTML = "<div class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>";
+ private static final String SHADOW_HTML = "<div aria-hidden=\"true\" class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>";
/**
* Matches {@link PopupPanel}.ANIMATION_DURATION
diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java
index fe29e2ebc0..2a949c31af 100644
--- a/client/src/com/vaadin/client/ui/VTabsheet.java
+++ b/client/src/com/vaadin/client/ui/VTabsheet.java
@@ -19,6 +19,10 @@ package com.vaadin.client.ui;
import java.util.Iterator;
import java.util.List;
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.LiveValue;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style;
@@ -55,6 +59,7 @@ import com.vaadin.client.TooltipInfo;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.ComponentStateUtil;
@@ -94,12 +99,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
+ "-selected";
private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
+ "-first";
+ private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME
+ + "-focus";
+ private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME
+ + "-first";
private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
+ "-disabled";
private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
+ "-selected";
+ private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME
+ + "-focus";
private TabCaption tabCaption;
Element td = getElement();
@@ -112,11 +123,17 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
private String styleName;
+ private String id;
+
private Tab(TabBar tabBar) {
super(DOM.createTD());
this.tabBar = tabBar;
setStyleName(td, TD_CLASSNAME);
+ Roles.getTabRole().set(getElement());
+ Roles.getTabRole().setAriaSelectedState(getElement(),
+ SelectedValue.FALSE);
+
div = DOM.createDiv();
focusImpl.setTabIndex(td, -1);
setStyleName(div, DIV_CLASSNAME);
@@ -127,6 +144,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
.getApplicationConnection());
add(tabCaption);
+ Roles.getTabRole().setAriaLabelledbyProperty(getElement(),
+ Id.of(tabCaption.getElement()));
+
addFocusHandler(getTabsheet());
addBlurHandler(getTabsheet());
addKeyDownHandler(getTabsheet());
@@ -138,6 +158,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public void setHiddenOnServer(boolean hiddenOnServer) {
this.hiddenOnServer = hiddenOnServer;
+ Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer);
}
@Override
@@ -152,6 +173,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public void setEnabledOnServer(boolean enabled) {
enabledOnServer = enabled;
+ Roles.getTabRole().setAriaDisabledState(getElement(), !enabled);
+
setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
if (!enabled) {
focusImpl.setTabIndex(td, -1);
@@ -175,10 +198,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
* true if the Tab is the first visible Tab
*/
public void setStyleNames(boolean selected, boolean first) {
+ setStyleNames(selected, first, false);
+ }
+
+ public void setStyleNames(boolean selected, boolean first,
+ boolean keyboardFocus) {
setStyleName(td, TD_FIRST_CLASSNAME, first);
setStyleName(td, TD_SELECTED_CLASSNAME, selected);
setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
+ setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus);
+ setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first);
+ setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus);
}
public void setTabulatorIndex(int tabIndex) {
@@ -204,10 +235,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
String newStyleName = tabUidl
.getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
// Find the nth td element
- if (newStyleName != null && newStyleName.length() != 0) {
+ if (newStyleName != null && !newStyleName.isEmpty()) {
if (!newStyleName.equals(styleName)) {
// If we have a new style name
- if (styleName != null && styleName.length() != 0) {
+ if (styleName != null && !styleName.isEmpty()) {
// Remove old style name if present
td.removeClassName(TD_CLASSNAME + "-" + styleName);
}
@@ -221,6 +252,15 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
td.removeClassName(TD_CLASSNAME + "-" + styleName);
styleName = null;
}
+
+ String newId = tabUidl.getStringAttribute("id");
+ if (newId != null && !newId.isEmpty()) {
+ td.setId(newId);
+ id = newId;
+ } else if (id != null) {
+ td.removeAttribute("id");
+ id = null;
+ }
}
public void recalculateCaptionWidth() {
@@ -249,6 +289,23 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public void blur() {
focusImpl.blur(td);
}
+
+ public boolean hasTooltip() {
+ return tabCaption.getTooltipInfo() != null;
+ }
+
+ public TooltipInfo getTooltipInfo() {
+ return tabCaption.getTooltipInfo();
+ }
+
+ public void setAssistiveDescription(String descriptionId) {
+ Roles.getTablistRole().setAriaDescribedbyProperty(getElement(),
+ Id.of(descriptionId));
+ }
+
+ public void removeAssistiveDescription() {
+ Roles.getTablistRole().removeAriaDescribedbyProperty(getElement());
+ }
}
public static class TabCaption extends VCaption {
@@ -262,6 +319,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
super(client);
this.client = client;
this.tab = tab;
+
+ AriaHelper.ensureHasId(getElement());
}
public boolean updateCaption(UIDL uidl) {
@@ -280,7 +339,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON),
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT));
setClosable(uidl.hasAttribute("closable"));
@@ -318,6 +378,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
closeButton.setInnerHTML("&times;");
closeButton
.setClassName(VTabsheet.CLASSNAME + "-caption-close");
+
+ Roles.getTabRole().setAriaHiddenState(closeButton, true);
+ Roles.getTabRole().setAriaDisabledState(closeButton, true);
+
getElement().appendChild(closeButton);
} else if (!closable && closeButton != null) {
getElement().removeChild(closeButton);
@@ -364,6 +428,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
this.tabsheet = tabsheet;
Element el = DOM.createTable();
+ Roles.getPresentationRole().set(el);
+
Element tbody = DOM.createTBody();
DOM.appendChild(el, tbody);
DOM.appendChild(tbody, tr);
@@ -420,11 +486,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
int index = getWidgetIndex(caption.getParent());
- // IE needs explicit focus()
- if (BrowserInfo.get().isIE()) {
- getTabsheet().focus();
- }
- getTabsheet().onTabSelected(index);
+
+ getTabsheet().focus();
+ getTabsheet().loadTabSheet(index);
}
public VTabsheet getTabsheet() {
@@ -442,13 +506,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
final Tab newSelected = getTab(index);
final Tab oldSelected = selected;
- newSelected.setStyleNames(true, isFirstVisibleTab(index));
+ newSelected.setStyleNames(true, isFirstVisibleTab(index), true);
newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
+ Roles.getTabRole().setAriaSelectedState(newSelected.getElement(),
+ SelectedValue.TRUE);
if (oldSelected != null && oldSelected != newSelected) {
oldSelected.setStyleNames(false,
isFirstVisibleTab(getWidgetIndex(oldSelected)));
oldSelected.setTabulatorIndex(-1);
+
+ Roles.getTabRole().setAriaSelectedState(
+ oldSelected.getElement(), SelectedValue.FALSE);
}
// Update the field holding the currently selected tab
@@ -459,6 +528,23 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
}
+ public void navigateTab(int fromIndex, int toIndex) {
+ Tab newNavigated = getTab(toIndex);
+ if (newNavigated == null) {
+ throw new IllegalArgumentException(
+ "Tab at provided index toIndex was not found");
+ }
+
+ Tab oldNavigated = getTab(fromIndex);
+ newNavigated.setStyleNames(newNavigated.equals(selected),
+ isFirstVisibleTab(toIndex), true);
+
+ if (oldNavigated != null && fromIndex != toIndex) {
+ oldNavigated.setStyleNames(oldNavigated.equals(selected),
+ isFirstVisibleTab(fromIndex), false);
+ }
+ }
+
public void removeTab(int i) {
Tab tab = getTab(i);
if (tab == null) {
@@ -591,7 +677,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* @return Whether the tab could be selected or not.
*/
- private boolean onTabSelected(final int tabIndex) {
+ private boolean canSelectTab(final int tabIndex) {
Tab tab = tb.getTab(tabIndex);
if (client == null || disabled || waitingForResponse) {
return false;
@@ -599,15 +685,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
return false;
}
- if (activeTabIndex != tabIndex) {
+
+ // Note that we return true when tabIndex == activeTabIndex; the active
+ // tab could be selected, it's just a no-op.
+ return true;
+ }
+
+ /**
+ * Load the content of a tab of the provided index.
+ *
+ * @param index
+ * of the tab to load
+ */
+ public void loadTabSheet(int tabIndex) {
+ 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 = tab;
+ focusedTab = tb.getTab(tabIndex);
+ focusedTab.focus();
}
+ activeTabIndex = tabIndex;
+ focusedTabIndex = tabIndex;
+
addStyleDependentName("loading");
// Hide the current contents so a loading indicator can be shown
// instead
@@ -619,9 +722,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
.toString(), true);
waitingForResponse = true;
}
- // Note that we return true when tabIndex == activeTabIndex; the active
- // tab could be selected, it's just a no-op.
- return true;
}
public ApplicationConnection getApplicationConnection() {
@@ -663,22 +763,30 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
DOM.setStyleAttribute(getElement(), "overflow", "hidden");
tabs = DOM.createDiv();
DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+ Roles.getTablistRole().set(tabs);
+ Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF);
scroller = DOM.createDiv();
+ Roles.getTablistRole().setAriaHiddenState(scroller, true);
DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
scrollerPrev = DOM.createButton();
+ scrollerPrev.setTabIndex(-1);
DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ "Prev");
+ Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true);
DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
scrollerNext = DOM.createButton();
+ scrollerNext.setTabIndex(-1);
DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+ "Next");
+ Roles.getTablistRole().setAriaHiddenState(scrollerNext, true);
DOM.sinkEvents(scrollerNext, Event.ONCLICK);
DOM.appendChild(getElement(), tabs);
// Tabs
tp.setStyleName(CLASSNAME + "-tabsheetpanel");
contentNode = DOM.createDiv();
+ Roles.getTabpanelRole().set(contentNode);
deco = DOM.createDiv();
@@ -1083,7 +1191,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
@Override
public void onBlur(BlurEvent event) {
+ getApplicationConnection().getVTooltip().hideTooltip();
+
if (focusedTab != null && event.getSource() instanceof Tab) {
+ focusedTab.removeAssistiveDescription();
focusedTab = null;
if (client.hasEventListeners(this, EventId.BLUR)) {
client.updateVariable(id, EventId.BLUR, "", true);
@@ -1098,6 +1209,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
if (client.hasEventListeners(this, EventId.FOCUS)) {
client.updateVariable(id, EventId.FOCUS, "", true);
}
+
+ if (focusedTab.hasTooltip()) {
+ focusedTab.setAssistiveDescription(getApplicationConnection()
+ .getVTooltip().getUniqueId());
+ getApplicationConnection().getVTooltip().showAssistive(
+ focusedTab.getTooltipInfo());
+ }
}
}
@@ -1117,13 +1235,17 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
if (keycode == getPreviousTabKey()) {
selectPreviousTab();
+ event.stopPropagation();
} else if (keycode == getNextTabKey()) {
selectNextTab();
+ event.stopPropagation();
} else if (keycode == getCloseTabKey()) {
Tab tab = tb.getTab(activeTabIndex);
if (tab.isClosable()) {
tab.onClose();
}
+ } else if (keycode == getSelectTabKey()) {
+ loadTabSheet(focusedTabIndex);
}
}
}
@@ -1136,6 +1258,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return KeyCodes.KEY_LEFT;
}
+ protected int getSelectTabKey() {
+ return 32; // Space key
+ }
+
/**
* @return The key code of the keyboard shortcut that selects the next tab
* in a focused tabsheet.
@@ -1153,18 +1279,27 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
private void selectPreviousTab() {
- int newTabIndex = activeTabIndex;
+ int newTabIndex = focusedTabIndex;
// Find the previous visible and enabled tab if any.
do {
newTabIndex--;
- } while (newTabIndex >= 0 && !onTabSelected(newTabIndex));
+ } while (newTabIndex >= 0 && !canSelectTab(newTabIndex));
if (newTabIndex >= 0) {
- activeTabIndex = newTabIndex;
+ tb.navigateTab(focusedTabIndex, newTabIndex);
+ focusedTabIndex = newTabIndex;
+
+ // If this TabSheet already has focus, set the new selected tab
+ // as focused.
+ if (focusedTab != null) {
+ focusedTab = tb.getTab(focusedTabIndex);
+ focusedTab.focus();
+ }
+
if (isScrolledTabs()) {
// Scroll until the new active tab is visible
int newScrollerIndex = scrollerIndex;
- while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
+ while (tb.getTab(focusedTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
&& newScrollerIndex != -1) {
newScrollerIndex = tb.scrollLeft(newScrollerIndex);
}
@@ -1175,18 +1310,28 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
private void selectNextTab() {
- int newTabIndex = activeTabIndex;
+ int newTabIndex = focusedTabIndex;
// Find the next visible and enabled tab if any.
do {
newTabIndex++;
- } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex));
+ } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));
if (newTabIndex < getTabCount()) {
- activeTabIndex = newTabIndex;
+
+ tb.navigateTab(focusedTabIndex, newTabIndex);
+ focusedTabIndex = newTabIndex;
+
+ // If this TabSheet already has focus, set the new selected tab
+ // as focused.
+ if (focusedTab != null) {
+ focusedTab = tb.getTab(focusedTabIndex);
+ focusedTab.focus();
+ }
+
if (isClippedTabs()) {
// Scroll until the new active tab is completely visible
int newScrollerIndex = scrollerIndex;
- while (isClipped(tb.getTab(activeTabIndex))
+ while (isClipped(tb.getTab(focusedTabIndex))
&& newScrollerIndex != -1) {
newScrollerIndex = tb.scrollRight(newScrollerIndex);
}
diff --git a/client/src/com/vaadin/client/ui/VTabsheetBase.java b/client/src/com/vaadin/client/ui/VTabsheetBase.java
index 0923248115..bcd8811c7d 100644
--- a/client/src/com/vaadin/client/ui/VTabsheetBase.java
+++ b/client/src/com/vaadin/client/ui/VTabsheetBase.java
@@ -42,6 +42,8 @@ public abstract class VTabsheetBase extends ComplexPanel {
/** For internal use only. May be removed or replaced in the future. */
public int activeTabIndex = 0;
/** For internal use only. May be removed or replaced in the future. */
+ public int focusedTabIndex = 0;
+ /** For internal use only. May be removed or replaced in the future. */
public boolean disabled;
/** For internal use only. May be removed or replaced in the future. */
public boolean readonly;
diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java
index 51c00ca310..1acd4bd05f 100644
--- a/client/src/com/vaadin/client/ui/VTree.java
+++ b/client/src/com/vaadin/client/ui/VTree.java
@@ -70,6 +70,7 @@ import com.vaadin.client.ui.dd.VDragEvent;
import com.vaadin.client.ui.dd.VDropHandler;
import com.vaadin.client.ui.dd.VHasDropHandler;
import com.vaadin.client.ui.dd.VTransferable;
+import com.vaadin.client.ui.tree.TreeConnector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.MouseEventDetails.MouseButton;
import com.vaadin.shared.ui.MultiSelectMode;
@@ -162,6 +163,9 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
/** For internal use only. May be removed or replaced in the future. */
public String[] bodyActionKeys;
+ /** For internal use only. May be removed or replaced in the future. */
+ public TreeConnector connector;
+
public VLazyExecutor iconLoaded = new VLazyExecutor(50,
new ScheduledCommand() {
@@ -1729,6 +1733,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
}
}
}
+ showTooltipForKeyboardNavigation(node);
return true;
}
@@ -1754,6 +1759,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
}
}
}
+ showTooltipForKeyboardNavigation(node);
return true;
}
@@ -1774,6 +1780,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
focusAndSelectNode(focusedNode.getParentNode());
}
}
+ showTooltipForKeyboardNavigation(focusedNode);
return true;
}
@@ -1792,6 +1799,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
focusAndSelectNode(focusedNode.getChildren().get(0));
}
}
+ showTooltipForKeyboardNavigation(focusedNode);
return true;
}
@@ -1820,6 +1828,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
selectNode(node, true);
}
sendSelectionToServer();
+ showTooltipForKeyboardNavigation(node);
return true;
}
@@ -1836,12 +1845,20 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
selectNode(node, true);
}
sendSelectionToServer();
+ showTooltipForKeyboardNavigation(node);
return true;
}
return false;
}
+ private void showTooltipForKeyboardNavigation(TreeNode node) {
+ if (connector != null) {
+ getClient().getVTooltip().showAssistive(
+ connector.getTooltipInfo(node.nodeCaptionSpan));
+ }
+ }
+
private void focusAndSelectNode(TreeNode node) {
/*
* Keyboard navigation doesn't work reliably if the tree is in
diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java
index 4817bf9304..1a84613a5d 100644
--- a/client/src/com/vaadin/client/ui/VUI.java
+++ b/client/src/com/vaadin/client/ui/VUI.java
@@ -18,6 +18,7 @@ package com.vaadin.client.ui;
import java.util.ArrayList;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.HasScrollHandlers;
@@ -43,6 +44,7 @@ import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.Profiler;
+import com.vaadin.client.Util;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
@@ -165,6 +167,8 @@ public class VUI extends SimplePanel implements ResizeHandler,
});
+ private Element storedFocus;
+
public VUI() {
super();
// Allow focusing the view by using the focus() method, the view
@@ -497,4 +501,36 @@ public class VUI extends SimplePanel implements ResizeHandler,
FocusUtil.setTabIndex(this, index);
}
+ /**
+ * Allows to store the currently focused Element.
+ *
+ * Current use case is to store the focus when a Window is opened. Does
+ * currently handle only a single value. Needs to be extended for #12158
+ *
+ * @param focusedElement
+ */
+ public void storeFocus() {
+ storedFocus = Util.getFocusedElement();
+ }
+
+ /**
+ * Restores the previously stored focus Element.
+ *
+ * Current use case is to restore the focus when a Window is closed. Does
+ * currently handle only a single value. Needs to be extended for #12158
+ *
+ * @return the lastFocusElementBeforeDialogOpened
+ */
+ public void focusStoredElement() {
+ if (storedFocus != null) {
+ storedFocus.focus();
+
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ storedFocus.focus();
+ }
+ });
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
index ff6a15e597..03ad8d03c8 100644
--- a/client/src/com/vaadin/client/ui/VWindow.java
+++ b/client/src/com/vaadin/client/ui/VWindow.java
@@ -18,10 +18,16 @@ package com.vaadin.client.ui;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
+import com.google.gwt.aria.client.Id;
+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.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
@@ -29,34 +35,45 @@ import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.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.Window;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.Util;
import com.vaadin.client.debug.internal.VDebugWindow;
import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.shared.Connector;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.window.WindowMode;
+import com.vaadin.shared.ui.window.WindowState.WindowRole;
/**
* "Sub window" component.
*
* @author Vaadin Ltd
*/
-public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
- ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
+public class VWindow extends VWindowOverlay implements
+ ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler,
+ KeyUpHandler, FocusHandler, BlurHandler, Focusable {
private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
@@ -138,6 +155,22 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
private boolean closable = true;
+ private Connector[] assistiveConnectors = new Connector[0];
+ private String assistivePrefix;
+ private String assistivePostfix;
+
+ private Element topTabStop;
+ private Element bottomTabStop;
+
+ private NativePreviewHandler topEventBlocker;
+ private NativePreviewHandler bottomEventBlocker;
+
+ private HandlerRegistration topBlockerRegistration;
+ private HandlerRegistration bottomBlockerRegistration;
+
+ // Prevents leaving the window with the Tab key when true
+ private boolean doTabStop;
+
/**
* If centered (via UIDL), the window should stay in the centered -mode
* until a position is received from the server, or the user moves or
@@ -172,13 +205,74 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
// Different style of shadow for windows
setShadowStyle("window");
+ Roles.getDialogRole().set(getElement());
+ Roles.getDialogRole().setAriaRelevantProperty(getElement(),
+ RelevantValue.ADDITIONS);
+
constructDOM();
contentPanel.addScrollHandler(this);
contentPanel.addKeyDownHandler(this);
+ contentPanel.addKeyUpHandler(this);
contentPanel.addFocusHandler(this);
contentPanel.addBlurHandler(this);
}
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+
+ /*
+ * Stores the element that has focus in the application UI when the
+ * window is opened, so it can be restored when the window closes.
+ *
+ * This is currently implemented for the case when one non-modal window
+ * can be open at the same time, and the focus is not changed while the
+ * window is open.
+ */
+ getApplicationConnection().getUIConnector().getWidget().storeFocus();
+
+ /*
+ * When this window gets reattached, set the tabstop to the previous
+ * state.
+ */
+ setTabStopEnabled(doTabStop);
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+
+ /*
+ * Restores the previously stored focused element.
+ *
+ * When the focus was changed outside the window while the window was
+ * open, the originally stored element is restored.
+ */
+ getApplicationConnection().getUIConnector().getWidget()
+ .focusStoredElement();
+
+ removeTabBlockHandlers();
+ }
+
+ private void addTabBlockHandlers() {
+ if (topBlockerRegistration == null) {
+ topBlockerRegistration = Event
+ .addNativePreviewHandler(topEventBlocker);
+ bottomBlockerRegistration = Event
+ .addNativePreviewHandler(bottomEventBlocker);
+ }
+ }
+
+ private void removeTabBlockHandlers() {
+ if (topBlockerRegistration != null) {
+ topBlockerRegistration.removeHandler();
+ topBlockerRegistration = null;
+
+ bottomBlockerRegistration.removeHandler();
+ bottomBlockerRegistration = null;
+ }
+ }
+
public void bringToFront() {
int curIndex = windowOrder.indexOf(this);
if (curIndex + 1 < windowOrder.size()) {
@@ -242,6 +336,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
protected void constructDOM() {
setStyleName(CLASSNAME);
+ topTabStop = DOM.createDiv();
+ DOM.setElementAttribute(topTabStop, "tabindex", "0");
+
header = DOM.createDiv();
DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
headerText = DOM.createDiv();
@@ -256,18 +353,25 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
maximizeRestoreBox = DOM.createDiv();
DOM.setElementProperty(maximizeRestoreBox, "className", CLASSNAME
+ "-maximizebox");
+ DOM.setElementAttribute(maximizeRestoreBox, "tabindex", "0");
DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
+ DOM.setElementAttribute(closeBox, "tabindex", "0");
DOM.appendChild(footer, resizeBox);
+ bottomTabStop = DOM.createDiv();
+ DOM.setElementAttribute(bottomTabStop, "tabindex", "0");
+
wrapper = DOM.createDiv();
DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
+ DOM.appendChild(wrapper, topTabStop);
DOM.appendChild(wrapper, header);
DOM.appendChild(wrapper, maximizeRestoreBox);
DOM.appendChild(wrapper, closeBox);
DOM.appendChild(header, headerText);
DOM.appendChild(wrapper, contents);
DOM.appendChild(wrapper, footer);
+ DOM.appendChild(wrapper, bottomTabStop);
DOM.appendChild(super.getContainerElement(), wrapper);
sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS
@@ -275,6 +379,96 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
setWidget(contentPanel);
+ // Make the closebox accessible for assistive devices
+ Roles.getButtonRole().set(closeBox);
+ Roles.getButtonRole().setAriaLabelProperty(closeBox, "close button");
+
+ // Make the maximizebox accessible for assistive devices
+ Roles.getButtonRole().set(maximizeRestoreBox);
+ Roles.getButtonRole().setAriaLabelProperty(maximizeRestoreBox,
+ "maximize button");
+
+ // Provide the title to assistive devices
+ AriaHelper.ensureHasId(headerText);
+ Roles.getDialogRole().setAriaLabelledbyProperty(getElement(),
+ Id.of(headerText));
+
+ // Handlers to Prevent tab to leave the window
+ topEventBlocker = new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (nativeEvent.getEventTarget().cast() == topTabStop
+ && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
+ && nativeEvent.getShiftKey()) {
+ nativeEvent.preventDefault();
+ }
+ }
+ };
+
+ bottomEventBlocker = new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (nativeEvent.getEventTarget().cast() == bottomTabStop
+ && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
+ && !nativeEvent.getShiftKey()) {
+ nativeEvent.preventDefault();
+ }
+ }
+ };
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param topMessage
+ * String provided when the user navigates with Shift-Tab keys to
+ * the top of the window
+ */
+ public void setTabStopTopAssistiveText(String topMessage) {
+ Roles.getNoteRole().setAriaLabelProperty(topTabStop, topMessage);
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param bottomMessage
+ * String provided when the user navigates with the Tab key to
+ * the bottom of the window
+ */
+ public void setTabStopBottomAssistiveText(String bottomMessage) {
+ Roles.getNoteRole().setAriaLabelProperty(bottomTabStop, bottomMessage);
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ *
+ * @return the top message
+ */
+ public String getTabStopTopAssistiveText() {
+ return Roles.getNoteRole().getAriaLabelProperty(topTabStop);
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ *
+ * @return the bottom message
+ */
+ public String getTabStopBottomAssistiveText() {
+ return Roles.getNoteRole().getAriaLabelProperty(bottomTabStop);
}
/**
@@ -492,6 +686,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
if (isAttached()) {
showModalityCurtain();
}
+ addTabBlockHandlers();
deferOrdering();
} else {
if (modalityCurtain != null) {
@@ -500,6 +695,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
modalityCurtain = null;
}
+ if (!doTabStop) {
+ removeTabBlockHandlers();
+ }
}
}
@@ -653,11 +851,64 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
if (icon != null) {
icon = client.translateVaadinUri(icon);
html = "<img src=\"" + Util.escapeAttribute(icon)
- + "\" class=\"v-icon\" />" + html;
+ + "\" class=\"v-icon\" alt=\"\" />" + html;
}
+
+ // Provide information to assistive device users that a sub window was
+ // opened
+ String prefix = "<span class='"
+ + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE + "'>"
+ + assistivePrefix + "</span>";
+ String postfix = "<span class='"
+ + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE + "'>"
+ + assistivePostfix + "</span>";
+
+ html = prefix + html + postfix;
DOM.setInnerHTML(headerText, html);
}
+ /**
+ * Setter for the text for assistive devices the window caption is prefixed
+ * with.
+ *
+ * @param assistivePrefix
+ * the assistivePrefix to set
+ */
+ public void setAssistivePrefix(String assistivePrefix) {
+ this.assistivePrefix = assistivePrefix;
+ }
+
+ /**
+ * Getter for the text for assistive devices the window caption is prefixed
+ * with.
+ *
+ * @return the assistivePrefix
+ */
+ public String getAssistivePrefix() {
+ return assistivePrefix;
+ }
+
+ /**
+ * Setter for the text for assistive devices the window caption is postfixed
+ * with.
+ *
+ * @param assistivePostfix
+ * the assistivePostfix to set
+ */
+ public void setAssistivePostfix(String assistivePostfix) {
+ this.assistivePostfix = assistivePostfix;
+ }
+
+ /**
+ * Getter for the text for assistive devices the window caption is postfixed
+ * with.
+ *
+ * @return the assistivePostfix
+ */
+ public String getAssistivePostfix() {
+ return assistivePostfix;
+ }
+
@Override
protected Element getContainerElement() {
// in GWT 1.5 this method is used in PopupPanel constructor
@@ -1026,6 +1277,13 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
@Override
+ public void onKeyUp(KeyUpEvent event) {
+ if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+ onCloseClick();
+ }
+ }
+
+ @Override
public void onBlur(BlurEvent event) {
if (client.hasEventListeners(this, EventId.BLUR)) {
client.updateVariable(id, EventId.BLUR, "", true);
@@ -1061,4 +1319,97 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
- contentPanel.getElement().getOffsetWidth();
}
+ /**
+ * Allows to specify which connectors contain the description for the
+ * window. Text contained in the widgets of the connectors will be read by
+ * assistive devices when it is opened.
+ * <p>
+ * When the provided array is empty, an existing description is removed.
+ *
+ * @param connectors
+ * with the connectors of the widgets to use as description
+ */
+ public void setAssistiveDescription(Connector[] connectors) {
+ if (connectors != null) {
+ assistiveConnectors = connectors;
+
+ if (connectors.length == 0) {
+ Roles.getDialogRole().removeAriaDescribedbyProperty(
+ getElement());
+ } else {
+ Id[] ids = new Id[connectors.length];
+ for (int index = 0; index < connectors.length; index++) {
+ if (connectors[index] == null) {
+ throw new IllegalArgumentException(
+ "All values in parameter description need to be non-null");
+ }
+
+ Element element = ((ComponentConnector) connectors[index])
+ .getWidget().getElement();
+ AriaHelper.ensureHasId(element);
+ ids[index] = Id.of(element);
+ }
+
+ Roles.getDialogRole().setAriaDescribedbyProperty(getElement(),
+ ids);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Parameter description must be non-null");
+ }
+ }
+
+ /**
+ * Gets the connectors that are used as assistive description. Text
+ * contained in these connectors will be read by assistive devices when the
+ * window is opened.
+ *
+ * @return list of previously set connectors
+ */
+ public List<Connector> getAssistiveDescription() {
+ return Collections.unmodifiableList(Arrays.asList(assistiveConnectors));
+ }
+
+ /**
+ * Sets the WAI-ARIA role the window.
+ *
+ * This role defines how an assistive device handles a window. Available
+ * roles are alertdialog and dialog (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>).
+ *
+ * The default role is dialog.
+ *
+ * @param role
+ * WAI-ARIA role to set for the window
+ */
+ public void setWaiAriaRole(WindowRole role) {
+ if ("alertdialog".equals(role)) {
+ Roles.getAlertdialogRole().set(getElement());
+ } else {
+ Roles.getDialogRole().set(getElement());
+ }
+ }
+
+ /**
+ * Registers the handlers that prevent to leave the window using the
+ * Tab-key.
+ * <p>
+ * The value of the parameter doTabStop is stored and used for non-modal
+ * windows. For modal windows, the handlers are always registered, while
+ * preserving the stored value.
+ *
+ * @param doTabStop
+ * true to prevent leaving the window, false to allow leaving the
+ * window for non modal windows
+ */
+ public void setTabStopEnabled(boolean doTabStop) {
+ this.doTabStop = doTabStop;
+
+ if (doTabStop || vaadinModality) {
+ addTabBlockHandlers();
+ } else {
+ removeTabBlockHandlers();
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VWindowOverlay.java b/client/src/com/vaadin/client/ui/VWindowOverlay.java
new file mode 100644
index 0000000000..efc01cf63e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/VWindowOverlay.java
@@ -0,0 +1,77 @@
+/*
+ * 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.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.vaadin.client.ApplicationConnection;
+
+public class VWindowOverlay extends VOverlay {
+ public VWindowOverlay() {
+ }
+
+ public VWindowOverlay(boolean autoHide, boolean modal, boolean showShadow) {
+ super(autoHide, modal, showShadow);
+ }
+
+ /**
+ * Gets the 'overlay container' element. Tries to find the current
+ * {@link ApplicationConnection} using {@link #getApplicationConnection()}.
+ *
+ * @return the overlay container element for the current
+ * {@link ApplicationConnection} or another element if the current
+ * {@link ApplicationConnection} cannot be determined.
+ */
+ @Override
+ public Element getOverlayContainer() {
+ ApplicationConnection ac = getApplicationConnection();
+ if (ac == null) {
+ return super.getOverlayContainer();
+ } else {
+ Element overlayContainer = getOverlayContainer(ac);
+ return overlayContainer;
+ }
+ }
+
+ /**
+ * Gets the 'overlay container' element pertaining to the given
+ * {@link ApplicationConnection}. Each overlay should be created in a
+ * overlay container element, so that the correct theme and styles can be
+ * applied.
+ *
+ * @param ac
+ * A reference to {@link ApplicationConnection}
+ * @return The overlay container
+ */
+ public static Element getOverlayContainer(ApplicationConnection ac) {
+ String id = ac.getConfiguration().getRootPanelId();
+ id = id += "-window-overlays";
+ Element container = DOM.getElementById(id);
+ if (container == null) {
+ container = DOM.createDiv();
+ container.setId(id);
+ String styles = ac.getUIConnector().getWidget().getParent()
+ .getStyleName();
+ container.addClassName(styles);
+ container.addClassName(CLASSNAME_CONTAINER);
+ RootPanel.get().getElement().appendChild(container);
+ }
+
+ return container;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
index a6630f28b9..9a63808742 100644
--- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java
+++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
@@ -87,8 +87,7 @@ public class ButtonConnector extends AbstractComponentConnector implements
getWidget().icon.getElement(),
getWidget().captionElement);
}
- getWidget().icon.setUri(getIcon());
- getWidget().icon.setAlternateText(getState().iconAltText);
+ getWidget().icon.setUri(getIcon(), getState().iconAltText);
} else {
if (getWidget().icon != null) {
getWidget().wrapper.removeChild(getWidget().icon
diff --git a/client/src/com/vaadin/client/ui/dd/VHtml5File.java b/client/src/com/vaadin/client/ui/dd/VHtml5File.java
index 4b36e7fd1b..c4ad615fbd 100644
--- a/client/src/com/vaadin/client/ui/dd/VHtml5File.java
+++ b/client/src/com/vaadin/client/ui/dd/VHtml5File.java
@@ -35,7 +35,13 @@ public class VHtml5File extends JavaScriptObject {
return this.type;
}-*/;
- public native final int getSize()
+ /*
+ * Browser implementations support files >2GB dropped and report the value
+ * as long. Due to JSNI limitations this value needs to be sent as double
+ * and then cast back to a long value.
+ * www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#important
+ */
+ public native final double getSize()
/*-{
return this.size ? this.size : 0;
}-*/;
diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
index ef016c31b7..6f89137918 100644
--- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java
+++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
@@ -45,6 +45,11 @@ public class TreeConnector extends AbstractComponentConnector implements
protected final Map<TreeNode, TooltipInfo> tooltipMap = new HashMap<TreeNode, TooltipInfo>();
@Override
+ protected void init() {
+ getWidget().connector = this;
+ }
+
+ @Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if (!isRealUpdate(uidl)) {
return;
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index c6d2e1436b..45005ddd9f 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -101,10 +101,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
protected void init() {
super.init();
registerRpc(PageClientRpc.class, new PageClientRpc() {
- @Override
- public void setTitle(String title) {
- com.google.gwt.user.client.Window.setTitle(title);
- }
@Override
public void reload() {
@@ -661,6 +657,11 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
configurePolling();
}
+ if (stateChangeEvent.hasPropertyChanged("pageState.title")) {
+ com.google.gwt.user.client.Window
+ .setTitle(getState().pageState.title);
+ }
+
if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) {
getConnection().setPushEnabled(
getState().pushConfiguration.mode.isEnabled());
diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java
index 4b839384a2..464ab386c1 100644
--- a/client/src/com/vaadin/client/ui/window/WindowConnector.java
+++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java
@@ -295,8 +295,18 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
if (getIcon() != null) {
iconURL = getIcon();
}
+
+ window.setAssistivePrefix(state.assistivePrefix);
+ window.setAssistivePostfix(state.assistivePostfix);
window.setCaption(state.caption, iconURL);
+ window.setWaiAriaRole(getState().role);
+ window.setAssistiveDescription(state.contentDescription);
+
+ window.setTabStopEnabled(getState().assistiveTabStop);
+ window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText);
+ window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText);
+
clickEventHandler.handleEventHandlerRegistration();
window.immediate = state.immediate;