height: 0;
}
+.v-assistive-device-only {
+ position: absolute;
+ left: -2000px;
+ width: 10px;
+ overflow: hidden;
+}
}
\ No newline at end of file
package com.vaadin.client;
+import com.google.gwt.aria.client.Roles;
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.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.AriaHelper;
import com.vaadin.client.ui.Icon;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.AbstractFieldState;
this.client = client;
owner = component;
+ AriaHelper.bindCaption(component.getWidget(), getElement());
+
if (client != null && owner != null) {
setOwnerPid(getElement(), owner.getConnectorId());
}
DOM.insertChild(getElement(), requiredFieldIndicator,
getInsertPosition(InsertPosition.REQUIRED));
+
+ Roles.getTextboxRole().setAriaRequiredProperty(
+ owner.getWidget().getElement(), true);
+
+ // Hide the required indicator from assistive device
+ Roles.getTextboxRole().setAriaHiddenState(
+ requiredFieldIndicator, true);
}
} else if (requiredFieldIndicator != null) {
// Remove existing
DOM.removeChild(getElement(), requiredFieldIndicator);
requiredFieldIndicator = null;
+
+ Roles.getTextboxRole().removeAriaRequiredProperty(
+ owner.getWidget().getElement());
}
if (showError) {
public VErrorMessage() {
super();
setStyleName(CLASSNAME);
+
+ // Needed for binding with WAI-ARIA attributes
+ getElement().setId(DOM.createUniqueId());
}
/**
*/
package com.vaadin.client;
+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.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
// Open next tooltip faster. Disabled after 2 sec of showTooltip-silence.
private boolean justClosed = false;
+ private String uniqueId = DOM.createUniqueId();
+ private Element layoutElement;
+
/**
* Used to show tooltips; usually used via the singleton in
* {@link ApplicationConnection}. NOTE that #setOwner(Widget)} should be
setWidget(layout);
layout.add(em);
DOM.setElementProperty(description, "className", CLASSNAME + "-text");
- DOM.appendChild(layout.getElement(), description);
+
+ layoutElement = layout.getElement();
+ DOM.appendChild(layoutElement, description);
setSinkShadowEvents(true);
+
+ // WAI-ARIA additions
+ layoutElement.setId(uniqueId);
+ Roles.getTooltipRole().setAriaLiveProperty(getElement(),
+ LiveValue.POLITE);
+
+ description.setId(DOM.createUniqueId());
+ Roles.getTooltipRole().set(layoutElement);
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
}
/**
closing = true;
justClosed = true;
justClosedTimer.schedule(QUICK_OPEN_TIMEOUT);
+ }
+ @Override
+ public void hide() {
+ super.hide();
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
+ Roles.getTooltipRole().removeAriaDescribedbyProperty(
+ tooltipEventHandler.currentElement);
}
private int tooltipEventMouseX;
private int tooltipEventMouseY;
- public void updatePosition(Event event) {
- tooltipEventMouseX = DOM.eventGetClientX(event);
- tooltipEventMouseY = DOM.eventGetClientY(event);
+ public void updatePosition(Event event, boolean isFocused) {
+ if (isFocused) {
+ tooltipEventMouseX = -1000;
+ tooltipEventMouseY = -1000;
+ } else {
+ tooltipEventMouseX = DOM.eventGetClientX(event);
+ tooltipEventMouseY = DOM.eventGetClientY(event);
+ }
}
@Override
}
private class TooltipEventHandler implements MouseMoveHandler,
- ClickHandler, KeyDownHandler {
+ ClickHandler, KeyDownHandler, FocusHandler, BlurHandler {
/**
* Current element hovered
*/
private com.google.gwt.dom.client.Element currentElement = null;
+ /**
+ * Current element focused
+ */
+ private boolean currentIsFocused;
+
/**
* Current tooltip active
*/
@Override
public void onMouseMove(MouseMoveEvent mme) {
- Event event = Event.as(mme.getNativeEvent());
+ handleShowHide(mme, false);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ handleHideEvent();
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ handleHideEvent();
+ }
+
+ /**
+ * Displays Tooltip when page is navigated with the keyboard.
+ *
+ * Tooltip is not visible. This makes it possible for assistive devices
+ * to recognize the tooltip.
+ */
+ @Override
+ public void onFocus(FocusEvent fe) {
+ handleShowHide(fe, true);
+ }
+
+ /**
+ * Hides Tooltip when the page is navigated with the keyboard.
+ *
+ * Removes the Tooltip from page to make sure assistive devices don't
+ * recognize it by accident.
+ */
+ @Override
+ public void onBlur(BlurEvent be) {
+ handleHideEvent();
+ }
+
+ private void handleShowHide(DomEvent domEvent, boolean isFocused) {
+ Event event = Event.as(domEvent.getNativeEvent());
com.google.gwt.dom.client.Element element = Element.as(event
.getEventTarget());
// We can ignore move event if it's handled by move or over already
- if (currentElement == element) {
+ if (currentElement == element && currentIsFocused == isFocused) {
return;
}
- currentElement = element;
boolean connectorAndTooltipFound = resolveConnector((com.google.gwt.user.client.Element) element);
if (!connectorAndTooltipFound) {
if (isShowing()) {
handleHideEvent();
+ Roles.getButtonRole()
+ .removeAriaDescribedbyProperty(element);
} else {
currentTooltipInfo = null;
}
} else {
- updatePosition(event);
+ updatePosition(event, isFocused);
+
if (isShowing()) {
replaceCurrentTooltip();
+ Roles.getTooltipRole().removeAriaDescribedbyProperty(
+ currentElement);
} else {
showTooltip();
}
- }
- }
- @Override
- public void onClick(ClickEvent event) {
- handleHideEvent();
- }
+ Roles.getTooltipRole().setAriaDescribedbyProperty(element,
+ Id.of(uniqueId));
+ }
- @Override
- public void onKeyDown(KeyDownEvent event) {
- handleHideEvent();
+ currentIsFocused = isFocused;
+ currentElement = element;
}
}
widget.addDomHandler(tooltipEventHandler, MouseMoveEvent.getType());
widget.addDomHandler(tooltipEventHandler, ClickEvent.getType());
widget.addDomHandler(tooltipEventHandler, KeyDownEvent.getType());
+ widget.addDomHandler(tooltipEventHandler, FocusEvent.getType());
+ widget.addDomHandler(tooltipEventHandler, BlurEvent.getType());
Profiler.leave("VTooltip.connectHandlersToWidget");
}
+
+ /**
+ * Returns the unique id of the tooltip element.
+ *
+ * @return String containing the unique id of the tooltip, which always has
+ * a value
+ */
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ @Override
+ public void setPopupPositionAndShow(PositionCallback callback) {
+ super.setPopupPositionAndShow(callback);
+
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, false);
+ }
}
* Helper class that helps to implement the WAI-ARIA functionality.
*/
public class AriaHelper {
+ public static final String ASSISTIVE_DEVICE_ONLY_STYLE = "v-assistive-device-only";
/**
* Binds a caption (label in HTML speak) to the form element as required by
public static void bindCaption(Widget widget, Element captionElement) {
assert widget != null : "Valid Widget required";
- if (null != captionElement) {
- ensureUniqueId(captionElement);
-
- if (widget instanceof HandlesAriaCaption) {
- ((HandlesAriaCaption) widget).handleAriaCaption(captionElement);
+ if (widget instanceof HandlesAriaCaption) {
+ // Let the widget handle special cases itself
+ if (captionElement == null) {
+ ((HandlesAriaCaption) widget).clearAriaCaption();
} else {
- String ownerId = ensureUniqueId(widget.getElement());
- captionElement.setAttribute("for", ownerId);
-
- Roles.getTextboxRole().setAriaLabelledbyProperty(
- widget.getElement(), Id.of(captionElement));
+ ensureUniqueId(captionElement);
+ ((HandlesAriaCaption) widget).bindAriaCaption(captionElement);
}
+ } else if (captionElement != null) {
+ // Handle the default case
+ ensureUniqueId(captionElement);
+ String ownerId = ensureUniqueId(widget.getElement());
+ captionElement.setAttribute("for", ownerId);
+
+ Roles.getTextboxRole().setAriaLabelledbyProperty(
+ widget.getElement(), Id.of(captionElement));
} else {
- Roles.getTextboxRole().removeAriaLabelledbyProperty(
- widget.getElement());
+ clearCaption(widget);
}
}
+ public static void clearCaption(Widget widget) {
+ Roles.getTextboxRole()
+ .removeAriaLabelledbyProperty(widget.getElement());
+ }
+
/**
* Handles the required actions depending of the input element being
* required or not.
* Element to check
* @return String with the id of the element
*/
- private static String ensureUniqueId(Element element) {
+ public static String ensureUniqueId(Element element) {
String id = element.getId();
if (null == id || id.isEmpty()) {
id = DOM.createUniqueId();
}
return id;
}
+
+ /**
+ * Moves an element out of sight. That way it is possible to have additional
+ * information for an assistive device, that is not in the way for visual
+ * users.
+ *
+ * @param element
+ * Element to move out of sight
+ */
+ public static void visibleForAssistiveDevicesOnly(Element element) {
+ element.addClassName(ASSISTIVE_DEVICE_ONLY_STYLE);
+ }
+
+ /**
+ * Clears the settings that moved the element out of sight, so it is visible
+ * on the page again.
+ *
+ * @param element
+ * Element to clear the specific styles from
+ */
+ public static void visibleForAll(Element element) {
+ element.removeClassName(ASSISTIVE_DEVICE_ONLY_STYLE);
+ }
}
* @param captionElement
* Element of the caption
*/
- void handleAriaCaption(Element captionElement);
+ void bindAriaCaption(Element captionElement);
+
+ /**
+ * Called to clear the binding to a caption from the main input element of
+ * the widget.
+ */
+ void clearAriaCaption();
}
import java.util.Date;
import java.util.Iterator;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.dom.client.Node;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
private boolean showISOWeekNumbers;
- private Date displayedMonth;
+ private FocusedDate displayedMonth;
- private Date focusedDate;
+ private FocusedDate focusedDate;
private Day selectedDay;
private boolean initialRenderDone = false;
public VCalendarPanel() {
-
+ getElement().setId(DOM.createUniqueId());
setStyleName(VDateField.CLASSNAME + "-calendarpanel");
+ Roles.getGridRole().set(getElement());
/*
* Firefox auto-repeat works correctly only if we use a key press
private void selectDate(Date date) {
if (selectedDay != null) {
selectedDay.removeStyleDependentName(CN_SELECTED);
+ Roles.getGridcellRole().removeAriaSelectedState(
+ selectedDay.getElement());
}
int rowCount = days.getRowCount();
if (curday.getDate().equals(date)) {
curday.addStyleDependentName(CN_SELECTED);
selectedDay = curday;
+ Roles.getGridcellRole().setAriaSelectedState(
+ selectedDay.getElement(), SelectedValue.TRUE);
return;
}
}
} else {
days.setHTML(headerRow, firstWeekdayColumn + i, "");
}
+
+ Roles.getColumnheaderRole().set(
+ days.getCellFormatter().getElement(headerRow,
+ firstWeekdayColumn + i));
}
// Zero out hours, minutes, seconds, and milliseconds to compare dates
if (curr.equals(selectedDate)) {
day.addStyleDependentName(CN_SELECTED);
+ Roles.getGridcellRole().setAriaSelectedState(
+ day.getElement(), SelectedValue.TRUE);
selectedDay = day;
}
if (curr.equals(today)) {
}
days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
+ Roles.getGridcellRole().set(
+ days.getCellFormatter().getElement(weekOfMonth,
+ firstWeekdayColumn + dayOfWeek));
// ISO week numbers if requested
days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
isShowISOWeekNumbers());
+
if (isShowISOWeekNumbers()) {
final String baseCssClass = parent.getStylePrimaryName()
+ "-calendarpanel-weeknumber";
if (focusedDate == null) {
Date now = new Date();
// focusedDate must have zero hours, mins, secs, millisecs
- focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
- displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
+ focusedDate = new FocusedDate(now.getYear(), now.getMonth(),
+ now.getDate());
+ displayedMonth = new FocusedDate(now.getYear(), now.getMonth(), 1);
}
if (getResolution().getCalendarField() <= Resolution.MONTH
*/
} else if (keycode == getResetKey() && !shift) {
// Restore showing value the selected value
- focusedDate = new Date(value.getYear(), value.getMonth(),
+ focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
+ 1);
renderCalendar();
return true;
}
if (value == null) {
focusedDate = displayedMonth = null;
} else {
- focusedDate = new Date(value.getYear(), value.getMonth(),
+ focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
+ 1);
}
// Re-render calendar if the displayed month is changed,
mouseTimer.cancel();
}
}
+
+ /**
+ * Helper class to inform the screen reader that the user changed the
+ * selected date. It sets the value of a field that is outside the view, and
+ * is defined as a live area. That way the screen reader recognizes the
+ * change and reads it to the user.
+ */
+ public class FocusedDate extends Date {
+
+ public FocusedDate(int year, int month, int date) {
+ super(year, month, date);
+ }
+
+ @Override
+ public void setTime(long time) {
+ super.setTime(time);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setDate(int date) {
+ super.setDate(date);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setMonth(int month) {
+ super.setMonth(month);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setYear(int year) {
+ super.setYear(year);
+ setLabel();
+ }
+
+ private void setLabel() {
+ if (parent instanceof VPopupCalendar) {
+ ((VPopupCalendar) parent).setFocusedDate(this);
+ }
+ }
+ }
}
package com.vaadin.client.ui;
+import com.google.gwt.aria.client.CheckedValue;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
public VCheckBox() {
setStyleName(CLASSNAME);
+ // Add a11y role "checkbox"
+ Roles.getCheckboxRole().set(getElement());
+ Roles.getCheckboxRole().setAriaCheckedState(getElement(),
+ CheckedValue.FALSE);
+
Element el = DOM.getFirstChild(getElement());
while (el != null) {
DOM.sinkEvents(el,
}
}
+ @Override
+ public void setValue(Boolean value, boolean fireEvents) {
+ setCheckedValue(value);
+ super.setValue(value, fireEvents);
+ }
+
+ private void setCheckedValue(Boolean value) {
+ CheckedValue checkedValue = value ? CheckedValue.TRUE
+ : CheckedValue.FALSE;
+ Roles.getCheckboxRole().setAriaCheckedState(getElement(), checkedValue);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ Roles.getCheckboxRole().setAriaDisabledState(getElement(), true);
+ }
}
import java.util.List;
import java.util.Set;
+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.Style;
DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL);
addCloseHandler(this);
+
+ Roles.getListRole().set(getElement());
}
/**
while (it.hasNext()) {
final FilterSelectSuggestion s = it.next();
final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+ Roles.getListitemRole().set(mi.getElement());
Util.sinkOnloadForImages(mi.getElement());
});
popupOpener.sinkEvents(Event.ONMOUSEDOWN);
+ Roles.getButtonRole().set(popupOpener.getElement());
+
panel.add(tb);
panel.add(popupOpener);
initWidget(panel);
+ Roles.getComboboxRole().set(panel.getElement());
+
tb.addKeyDownHandler(this);
tb.addKeyUpHandler(this);
tb.addBlurHandler(this);
tb.addClickHandler(this);
+ Roles.getTextboxRole().set(tb.getElement());
+
popupOpener.addClickHandler(this);
setStyleName(CLASSNAME);
// Always update styles as they might have been overwritten
if (textInputEnabled) {
removeStyleDependentName(STYLE_NO_INPUT);
+ Roles.getTextboxRole().removeAriaReadonlyProperty(tb.getElement());
} else {
addStyleDependentName(STYLE_NO_INPUT);
+ Roles.getTextboxRole().setAriaReadonlyProperty(tb.getElement(),
+ true);
}
if (this.textInputEnabled == textInputEnabled) {
import java.util.HashMap;
import java.util.List;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
if (state.caption != null) {
if (captionText == null) {
captionText = DOM.createSpan();
+
+ AriaHelper.bindCaption(owner.getWidget(), captionText);
+
DOM.insertChild(getElement(), captionText, icon == null ? 0
: 1);
}
DOM.setElementProperty(requiredFieldIndicator, "className",
"v-required-field-indicator");
DOM.appendChild(getElement(), requiredFieldIndicator);
+
+ Roles.getTextboxRole().setAriaRequiredProperty(
+ owner.getWidget().getElement(), true);
+
+ // Hide the required indicator from screen reader, as this
+ // information is set directly at the input field
+ Roles.getTextboxRole().setAriaHiddenState(
+ requiredFieldIndicator, true);
}
} else {
if (requiredFieldIndicator != null) {
DOM.removeChild(getElement(), requiredFieldIndicator);
requiredFieldIndicator = null;
+
+ Roles.getTextboxRole().removeAriaRequiredProperty(
+ owner.getWidget().getElement());
}
}
import java.util.List;
import java.util.Map;
+import com.google.gwt.aria.client.CheckedValue;
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Focusable;
import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
- BlurHandler {
+ BlurHandler, HandlesAriaCaption {
public static final String CLASSNAME = "v-select-optiongroup";
/** For internal use only. May be removed or replaced in the future. */
public boolean htmlContentAllowed = false;
+ private String labelId;
+
public VOptionGroup() {
super(CLASSNAME);
panel = (Panel) optionsContainer;
public void buildOptions(UIDL uidl) {
panel.clear();
optionsEnabled.clear();
+
+ if (isMultiselect()) {
+ Roles.getGroupRole().set(getElement());
+ } else {
+ Roles.getRadiogroupRole().set(getElement());
+ }
+
for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
final UIDL opUidl = (UIDL) it.next();
CheckBox op;
if (isMultiselect()) {
op = new VCheckBox();
op.setHTML(itemHtml);
+
+ // Add a11y role "checkbox" - FIXME - did not find a good
+ // solution to prevent getFirstChild()
+ com.google.gwt.dom.client.Element checkBoxElement = op
+ .getElement().getFirstChildElement();
+ Roles.getCheckboxRole().set(checkBoxElement);
+ Roles.getCheckboxRole().setAriaCheckedState(checkBoxElement,
+ CheckedValue.FALSE);
} else {
op = new RadioButton(paintableId, itemHtml, true);
op.setStyleName("v-radiobutton");
+
+ // Add a11y role "radio" - FIXME - did not find a good solution
+ // to prevent getFirstChild()
+ com.google.gwt.dom.client.Element radioElement = op
+ .getElement().getFirstChildElement();
+ Roles.getRadioRole().set(radioElement);
+ Roles.getRadioRole().setAriaCheckedState(radioElement,
+ CheckedValue.FALSE);
+ }
+
+ if (labelId != null && !labelId.isEmpty()) {
+ Roles.getFormRole().setAriaDescribedbyProperty(
+ op.getElement().getFirstChildElement(), Id.of(labelId));
}
if (icon != null && icon.length() != 0) {
}
client.updateVariable(paintableId, "selected", getSelectedItems(),
isImmediate());
+
+ for (CheckBox item : optionsToKeys.keySet()) {
+ CheckedValue value = item.getValue() ? CheckedValue.TRUE
+ : CheckedValue.FALSE;
+ Roles.getCheckboxRole().setAriaCheckedState(item.getElement(),
+ value);
+ }
}
}
});
}
}
+
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ labelId = captionElement.getId();
+ for (CheckBox item : optionsToKeys.keySet()) {
+ Roles.getCheckboxRole().setAriaLabelledbyProperty(
+ item.getElement(), Id.of(labelId));
+ }
+ }
+
+ @Override
+ public void clearAriaCaption() {
+ labelId = null;
+ for (CheckBox item : optionsToKeys.keySet()) {
+ Roles.getCheckboxRole().removeAriaLabelledbyProperty(
+ item.getElement());
+ }
+ }
}
import java.util.Date;
+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.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.i18n.client.DateTimeFormat;
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.Window;
import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
private boolean textFieldEnabled = true;
+ private String captionId;
+
+ private Label selectedDate;
+
public VPopupCalendar() {
super();
calendarToggle.addClickHandler(this);
// -2 instead of -1 to avoid FocusWidget.onAttach to reset it
calendarToggle.getElement().setTabIndex(-2);
+
+ Roles.getButtonRole().set(calendarToggle.getElement());
+ Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(),
+ true);
+
add(calendarToggle);
calendar = GWT.create(VCalendarPanel.class);
}
});
+ Roles.getButtonRole().setAriaControlsProperty(
+ calendarToggle.getElement(), Id.of(calendar.getElement()));
+
calendar.setSubmitListener(new SubmitListener() {
@Override
public void onSubmit() {
popup = new VOverlay(true, true, true);
popup.setOwner(this);
- popup.setWidget(calendar);
+ FlowPanel wrapper = new FlowPanel();
+ selectedDate = new Label();
+ selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate");
+ AriaHelper.visibleForAssistiveDevicesOnly(selectedDate.getElement());
+
+ Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(),
+ LiveValue.ASSERTIVE);
+ Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(),
+ true);
+ wrapper.add(selectedDate);
+ wrapper.add(calendar);
+
+ popup.setWidget(wrapper);
popup.addCloseHandler(this);
DOM.setElementProperty(calendar.getElement(), "id",
text.setEnabled(textFieldEnabled);
if (textFieldEnabled) {
calendarToggle.setTabIndex(-1);
+ Roles.getButtonRole().setAriaHiddenState(
+ calendarToggle.getElement(), true);
} else {
calendarToggle.setTabIndex(0);
+ Roles.getButtonRole().setAriaHiddenState(
+ calendarToggle.getElement(), false);
+ }
+
+ handleAriaAttributes();
+ }
+
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ captionId = captionElement.getId();
+
+ if (isTextFieldEnabled()) {
+ super.bindAriaCaption(captionElement);
+ } else {
+ AriaHelper.bindCaption(calendarToggle, captionElement);
+ }
+
+ handleAriaAttributes();
+ }
+
+ private void handleAriaAttributes() {
+ Widget removeFromWidget;
+ Widget setForWidget;
+
+ if (isTextFieldEnabled()) {
+ setForWidget = text;
+ removeFromWidget = calendarToggle;
+ } else {
+ setForWidget = calendarToggle;
+ removeFromWidget = text;
}
+
+ Roles.getFormRole().removeAriaLabelledbyProperty(
+ removeFromWidget.getElement());
+ if (captionId == null) {
+ Roles.getFormRole().removeAriaLabelledbyProperty(
+ setForWidget.getElement());
+ } else {
+ Roles.getFormRole().setAriaLabelledbyProperty(
+ setForWidget.getElement(), Id.of(captionId));
+ }
+ }
+
+ @Override
+ public void clearAriaCaption() {
+ captionId = null;
+ if (isTextFieldEnabled()) {
+ super.clearAriaCaption();
+ } else {
+ AriaHelper.clearCaption(calendarToggle);
+ }
+
+ handleAriaAttributes();
}
/*
calendar.setFocus(focus);
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ if (enabled) {
+ Roles.getButtonRole().setAriaDisabledState(
+ calendarToggle.getElement(), true);
+ } else {
+ Roles.getButtonRole().setAriaDisabledState(
+ calendarToggle.getElement(), false);
+ }
+ }
+
+ /**
+ * Sets the content of a special field for assistive devices, so that they
+ * can recognize the change and inform the user (reading out in case of
+ * screen reader)
+ *
+ * @param selectedDate
+ * Date that is currently selected
+ */
+ public void setFocusedDate(Date selectedDate) {
+ this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy")
+ .format(selectedDate));
+ }
+
/**
* For internal use only. May be removed or replaced in the future.
*
package com.vaadin.client.ui;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
}
addFocusHandler(this);
addBlurHandler(this);
+
+ // Add a11y role "textbox"
+ Roles.getTextboxRole().set(node);
}
/**
import java.util.Date;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.vaadin.shared.ui.datefield.Resolution;
public class VTextualDate extends VDateField implements Field, ChangeHandler,
- Focusable, SubPartAware {
+ Focusable, SubPartAware, HandlesAriaCaption {
private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
}
}
});
+
+ Roles.getTextboxRole().set(text.getElement());
+
add(text);
}
return formatStr;
}
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ AriaHelper.bindCaption(text, captionElement);
+ }
+
+ @Override
+ public void clearAriaCaption() {
+ AriaHelper.clearCaption(text);
+ }
+
/**
* Updates the text field according to the current date (provided by
* {@link #getDate()}). Takes care of updating text, enabling and disabling
if (readonly) {
text.addStyleName("v-readonly");
+ Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(),
+ true);
} else {
text.removeStyleName("v-readonly");
+ Roles.getTextboxRole()
+ .removeAriaReadonlyProperty(text.getElement());
}
}
return null;
}
-
}
import java.util.Iterator;
import java.util.Map;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Element;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.TooltipInfo;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.VTree;
}
childTree = getWidget().new TreeNode();
getConnection().getVTooltip().connectHandlersToWidget(childTree);
- updateNodeFromUIDL(childTree, childUidl);
+ updateNodeFromUIDL(childTree, childUidl, 1);
getWidget().body.add(childTree);
childTree.addStyleDependentName("root");
childTree.childNodeContainer.addStyleDependentName("root");
getWidget().isMultiselect = "multi".equals(selectMode);
if (getWidget().isMultiselect) {
+ Roles.getTreeRole().setAriaMultiselectableProperty(
+ getWidget().getElement(), true);
+
if (BrowserInfo.get().isTouchDevice()) {
// Always use the simple mode for touch devices that do not have
// shift/ctrl keys (#8595)
getWidget().multiSelectMode = MultiSelectMode.valueOf(uidl
.getStringAttribute("multiselectmode"));
}
+ } else {
+ Roles.getTreeRole().setAriaMultiselectableProperty(
+ getWidget().getElement(), false);
}
getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected");
// expanding node happened server side
rootNode.setState(true, false);
}
- renderChildNodes(rootNode, (Iterator) uidl.getChildIterator());
+ String levelPropertyString = Roles.getTreeitemRole()
+ .getAriaLevelProperty(rootNode.getElement());
+ int levelProperty;
+ try {
+ levelProperty = Integer.valueOf(levelPropertyString);
+ } catch (NumberFormatException e) {
+ levelProperty = 1;
+ VConsole.error(e);
+ }
+
+ renderChildNodes(rootNode, (Iterator) uidl.getChildIterator(),
+ levelProperty + 1);
}
}
}
- public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) {
+ public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl, int level) {
+ Roles.getTreeitemRole().setAriaLevelProperty(treeNode.getElement(),
+ level);
+
String nodeKey = uidl.getStringAttribute("key");
treeNode.setText(uidl
.getStringAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION));
if (uidl.getChildCount() == 0) {
treeNode.childNodeContainer.setVisible(false);
} else {
- renderChildNodes(treeNode, (Iterator) uidl.getChildIterator());
+ renderChildNodes(treeNode, (Iterator) uidl.getChildIterator(),
+ level + 1);
treeNode.childrenLoaded = true;
}
} else {
.getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON));
}
- void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i) {
+ void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i, int level) {
containerNode.childNodeContainer.clear();
containerNode.childNodeContainer.setVisible(true);
while (i.hasNext()) {
}
final TreeNode childTree = getWidget().new TreeNode();
getConnection().getVTooltip().connectHandlersToWidget(childTree);
- updateNodeFromUIDL(childTree, childUidl);
+ updateNodeFromUIDL(childTree, childUidl, level);
containerNode.childNodeContainer.add(childTree);
if (!i.hasNext()) {
childTree
package com.vaadin.tests.layouts;
-
import java.util.ArrayList;
import java.util.List;