summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Wagner <wbadam@users.noreply.github.com>2018-01-31 11:26:59 +0200
committerIlia Motornyi <elmot@vaadin.com>2018-01-31 11:26:59 +0200
commit75b98bee62185c858a875393d50c3fa5e2fe64e7 (patch)
tree7bc8f3ab6360bf4c1f6758c49faddee0ea9900fb
parent5b9d0b9175f9ce2f20d728db844b6fd03fea1461 (diff)
downloadvaadin-framework-75b98bee62185c858a875393d50c3fa5e2fe64e7.tar.gz
vaadin-framework-75b98bee62185c858a875393d50c3fa5e2fe64e7.zip
Add ARIA label support to DateField (#10538)
Fixes #10454
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java147
-rw-r--r--client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java34
-rw-r--r--client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java6
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractDateField.java28
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java27
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldAria.java45
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/datefield/DateFields.java2
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldAriaTest.java59
8 files changed, 338 insertions, 10 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java
index 21ea8ba4c4..8810b26e30 100644
--- a/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java
+++ b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java
@@ -25,6 +25,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import com.google.gwt.aria.client.Id;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.dom.client.Element;
@@ -56,6 +57,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.DateTimeService;
import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.util.SharedUtil;
/**
@@ -167,6 +169,14 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
private boolean initialRenderDone = false;
+ private String prevMonthAssistiveLabel;
+
+ private String nextMonthAssistiveLabel;
+
+ private String prevYearAssistiveLabel;
+
+ private String nextYearAssistiveLabel;
+
/**
* Represents a click handler for when a user selects a value by using the
* mouse
@@ -247,6 +257,13 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
if (curday.getDate().equals(date)) {
curday.addStyleDependentName(CN_FOCUSED);
focusedDay = curday;
+
+ // Reference focused day from calendar panel
+ Roles.getGridRole()
+ .setAriaActivedescendantProperty(
+ getElement(),
+ Id.of(curday.getElement()));
+
return;
}
}
@@ -490,8 +507,10 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
*
* @param needsMonth
* Should the month buttons be visible?
+ * @param needsBody
+ * indicates whether the calendar body is drawn
*/
- private void buildCalendarHeader(boolean needsMonth) {
+ private void buildCalendarHeader(boolean needsMonth, boolean needsBody) {
getRowFormatter().addStyleName(0,
parent.getStylePrimaryName() + "-calendarpanel-header");
@@ -501,16 +520,20 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
prevMonth.setHTML("&lsaquo;");
prevMonth.setStyleName("v-button-prevmonth");
- prevMonth.setTabIndex(-1);
-
nextMonth = new VEventButton();
nextMonth.setHTML("&rsaquo;");
nextMonth.setStyleName("v-button-nextmonth");
- nextMonth.setTabIndex(-1);
-
setWidget(0, 3, nextMonth);
setWidget(0, 1, prevMonth);
+
+ Roles.getButtonRole().set(prevMonth.getElement());
+ Roles.getButtonRole()
+ .setTabindexExtraAttribute(prevMonth.getElement(), -1);
+
+ Roles.getButtonRole().set(nextMonth.getElement());
+ Roles.getButtonRole()
+ .setTabindexExtraAttribute(nextMonth.getElement(), -1);
} else if (prevMonth != null && !needsMonth) {
// Remove month traverse buttons
remove(prevMonth);
@@ -525,18 +548,26 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
prevYear.setHTML("&laquo;");
prevYear.setStyleName("v-button-prevyear");
- prevYear.setTabIndex(-1);
nextYear = new VEventButton();
nextYear.setHTML("&raquo;");
nextYear.setStyleName("v-button-nextyear");
- nextYear.setTabIndex(-1);
setWidget(0, 0, prevYear);
setWidget(0, 4, nextYear);
+
+ Roles.getButtonRole().set(prevYear.getElement());
+ Roles.getButtonRole()
+ .setTabindexExtraAttribute(prevYear.getElement(), -1);
+
+ Roles.getButtonRole().set(nextYear.getElement());
+ Roles.getButtonRole()
+ .setTabindexExtraAttribute(nextYear.getElement(), -1);
}
updateControlButtonRangeStyles(needsMonth);
+ updateAssistiveLabels();
+
final String monthName = needsMonth
? getDateTimeService().getMonth(displayedMonth.getMonth())
: "";
@@ -553,6 +584,16 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
getFlexCellFormatter().setStyleName(0, 1,
parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
+ // Set ID to be referenced from focused date or calendar panel
+ Element monthYearElement = getFlexCellFormatter().getElement(0, 2);
+ AriaHelper.ensureHasId(monthYearElement);
+ if (!needsBody) {
+ Roles.getGridRole().setAriaLabelledbyProperty(getElement(),
+ Id.of(monthYearElement));
+ } else {
+ Roles.getGridRole().removeAriaLabelledbyProperty(getElement());
+ }
+
setHTML(0, 2,
"<span class=\"" + parent.getStylePrimaryName()
+ "-calendarpanel-month\">" + monthName + " " + year
@@ -830,6 +871,17 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
Date dayDate = (Date) curr.clone();
Day day = new Day(dayDate);
+ // Set ID with prefix of the calendar panel's ID
+ day.getElement().setId(getElement().getId() + "-" + weekOfMonth
+ + "-" + dayOfWeek);
+
+ // Set assistive label to read focused date and month/year
+ Roles.getButtonRole().set(day.getElement());
+ Roles.getButtonRole()
+ .setAriaLabelledbyProperty(day.getElement(),
+ Id.of(day.getElement()),
+ Id.of(getFlexCellFormatter().getElement(0, 2)));
+
day.setStyleName(getDateField().getStylePrimaryName()
+ "-calendarpanel-day");
@@ -850,6 +902,11 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
focusedDay = day;
if (hasFocus) {
day.addStyleDependentName(CN_FOCUSED);
+
+ // Reference focused day from calendar panel
+ Roles.getGridRole()
+ .setAriaActivedescendantProperty(getElement(),
+ Id.of(day.getElement()));
}
}
if (curr.getMonth() != displayedMonth.getMonth()) {
@@ -940,7 +997,7 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
final boolean needsMonth = !isYear(getResolution());
boolean needsBody = isBelowMonth(resolution);
- buildCalendarHeader(needsMonth);
+ buildCalendarHeader(needsMonth, needsBody);
clearCalendarBody(!needsBody);
if (needsBody) {
buildCalendarBody();
@@ -1229,7 +1286,6 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
event.getNativeEvent().getShiftKey())) {
event.preventDefault();
}
-
}
/**
@@ -1345,7 +1401,7 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
renderCalendar();
return true;
- } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
+ } else if (keycode == getCloseKey()) {
onCancel();
// TODO fire close event
@@ -2043,6 +2099,77 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
}
}
+ /**
+ * Set assistive label for the previous year element.
+ *
+ * @param label
+ * the label to set
+ * @since
+ */
+ public void setAssistiveLabelPreviousYear(String label) {
+ prevYearAssistiveLabel = label;
+ }
+
+ /**
+ * Set assistive label for the next year element.
+ *
+ * @param label
+ * the label to set
+ * @since
+ */
+ public void setAssistiveLabelNextYear(String label) {
+ nextYearAssistiveLabel = label;
+ }
+
+ /**
+ * Set assistive label for the previous month element.
+ *
+ * @param label
+ * the label to set
+ * @since
+ */
+ public void setAssistiveLabelPreviousMonth(String label) {
+ prevMonthAssistiveLabel = label;
+ }
+
+ /**
+ * Set assistive label for the next month element.
+ *
+ * @param label
+ * the label to set
+ * @since
+ */
+ public void setAssistiveLabelNextMonth(String label) {
+ nextMonthAssistiveLabel = label;
+ }
+
+ /**
+ * Updates assistive labels of the navigation elements.
+ *
+ * @since
+ */
+ public void updateAssistiveLabels() {
+ if (prevMonth != null) {
+ Roles.getButtonRole().setAriaLabelProperty(prevMonth.getElement(),
+ prevMonthAssistiveLabel);
+ }
+
+ if (nextMonth != null) {
+ Roles.getButtonRole().setAriaLabelProperty(nextMonth.getElement(),
+ nextMonthAssistiveLabel);
+ }
+
+ if (prevYear != null) {
+ Roles.getButtonRole().setAriaLabelProperty(prevYear.getElement(),
+ prevYearAssistiveLabel);
+ }
+
+ if (nextYear != null) {
+ Roles.getButtonRole().setAriaLabelProperty(nextYear.getElement(),
+ nextYearAssistiveLabel);
+ }
+ }
+
private static Logger getLogger() {
return Logger.getLogger(VAbstractCalendarPanel.class.getName());
}
diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java
index 4b52ee50f6..35d1c1c4d1 100644
--- a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java
+++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java
@@ -24,11 +24,15 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vaadin.client.LocaleNotLoadedException;
+import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VAbstractCalendarPanel;
+import com.vaadin.client.ui.VAbstractPopupCalendar;
import com.vaadin.client.ui.VDateField;
import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
+import com.vaadin.shared.ui.datefield.AbstractDateFieldState.AccessibleElement;
public abstract class AbstractDateFieldConnector<R extends Enum<R>>
extends AbstractFieldConnector {
@@ -133,6 +137,36 @@ public abstract class AbstractDateFieldConnector<R extends Enum<R>>
widget.setDefaultDate(getDefaultValues());
}
+ @OnStateChange("assistiveLabels")
+ private void updateAssistiveLabels() {
+ if (getWidget() instanceof VAbstractPopupCalendar) {
+ setAndUpdateAssistiveLabels(
+ ((VAbstractPopupCalendar) getWidget()).calendar);
+ }
+ }
+
+ /**
+ * Sets assistive labels for the calendar panel's navigation elements, and
+ * updates these labels.
+ *
+ * @param calendar
+ * the calendar panel for which to set the assistive labels
+ * @since
+ */
+ protected void setAndUpdateAssistiveLabels(
+ VAbstractCalendarPanel calendar) {
+ calendar.setAssistiveLabelPreviousMonth(
+ getState().assistiveLabels.get(AccessibleElement.PREVIOUS_MONTH));
+ calendar.setAssistiveLabelNextMonth(
+ getState().assistiveLabels.get(AccessibleElement.NEXT_MONTH));
+ calendar.setAssistiveLabelPreviousYear(
+ getState().assistiveLabels.get(AccessibleElement.PREVIOUS_YEAR));
+ calendar.setAssistiveLabelNextYear(
+ getState().assistiveLabels.get(AccessibleElement.NEXT_YEAR));
+
+ calendar.updateAssistiveLabels();
+ }
+
private static Logger getLogger() {
return Logger.getLogger(AbstractDateFieldConnector.class.getName());
}
diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java
index 2492272111..51852de708 100644
--- a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java
+++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java
@@ -19,6 +19,7 @@ import java.util.Date;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.UIDL;
+import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.VAbstractCalendarPanel;
import com.vaadin.client.ui.VAbstractDateFieldCalendar;
@@ -99,6 +100,11 @@ public abstract class AbstractInlineDateFieldConnector<PANEL extends VAbstractCa
widget.calendarPanel.renderCalendar();
}
+ @OnStateChange("assistiveLabels")
+ private void updateAssistiveLabels() {
+ setAndUpdateAssistiveLabels(getWidget().calendarPanel);
+ }
+
@Override
@SuppressWarnings("unchecked")
public VAbstractDateFieldCalendar<PANEL, R> getWidget() {
diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java
index 110223c628..3998afbae6 100644
--- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java
+++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java
@@ -55,6 +55,7 @@ import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
+import com.vaadin.shared.ui.datefield.AbstractDateFieldState.AccessibleElement;
import com.vaadin.shared.ui.datefield.DateResolution;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
@@ -875,4 +876,31 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
}
return Collections.unmodifiableMap(hashMap);
}
+
+ /**
+ * Sets the assistive label for a calendar navigation element. This sets the
+ * {@code aria-label} attribute for the element which is used by screen
+ * reading software.
+ *
+ * @param element
+ * the element for which to set the label. Not {@code null}.
+ * @param label
+ * the assistive label to set
+ * @since
+ */
+ public void setAssistiveLabel(AccessibleElement element, String label) {
+ Objects.requireNonNull(element, "Element cannot be null");
+ getState().assistiveLabels.put(element, label);
+ }
+
+ /**
+ * Gets the assistive label of a calendar navigation element.
+ *
+ * @param element
+ * the element of which to get the assistive label
+ * @since
+ */
+ public void getAssistiveLabel(AccessibleElement element) {
+ getState(false).assistiveLabels.get(element);
+ }
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java b/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java
index 69bc065cff..553e82a57e 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java
@@ -31,6 +31,18 @@ import com.vaadin.shared.annotations.NoLayout;
*/
public class AbstractDateFieldState extends AbstractFieldState {
+ /**
+ * Navigation elements that have assistive label.
+ *
+ * @since
+ */
+ public enum AccessibleElement {
+ PREVIOUS_YEAR,
+ NEXT_YEAR,
+ PREVIOUS_MONTH,
+ NEXT_MONTH
+ }
+
{
primaryStyleName = "v-datefield";
}
@@ -114,4 +126,19 @@ public class AbstractDateFieldState extends AbstractFieldState {
*/
public Map<String, String> dateStyles = new HashMap<String, String>();
+ /**
+ * Map of elements and their corresponding assistive labels.
+ *
+ * @since
+ */
+ public Map<AccessibleElement, String> assistiveLabels = new HashMap<>();
+
+ // Set default accessive labels
+ {
+ assistiveLabels.put(AccessibleElement.PREVIOUS_YEAR, "Previous year");
+ assistiveLabels.put(AccessibleElement.NEXT_YEAR, "Next year");
+ assistiveLabels.put(AccessibleElement.PREVIOUS_MONTH, "Previous month");
+ assistiveLabels.put(AccessibleElement.NEXT_MONTH, "Next month");
+ }
+
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldAria.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldAria.java
new file mode 100644
index 0000000000..b74b1db3df
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldAria.java
@@ -0,0 +1,45 @@
+package com.vaadin.tests.components.datefield;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.datefield.AbstractDateFieldState.AccessibleElement;
+import com.vaadin.shared.ui.datefield.DateResolution;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.DateField;
+import com.vaadin.ui.InlineDateField;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class DateFieldAria extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ DateField dateField = new DateField("Accessible DateField",
+ LocalDate.now());
+ addComponent(dateField);
+
+ InlineDateField inlineDateField = new InlineDateField(
+ "Accessible InlineDateField", LocalDate.now());
+ addComponent(inlineDateField);
+
+ ComboBox<DateResolution> resolutions = new ComboBox<>("Date resolution",
+ Arrays.asList(DateResolution.values()));
+ resolutions.setValue(DateResolution.DAY);
+ resolutions.addValueChangeListener(e -> {
+ dateField.setResolution(e.getValue());
+ inlineDateField.setResolution(e.getValue());
+ });
+ addComponent(resolutions);
+
+ addComponent(new Button("Change assistive labels", e -> {
+ dateField.setAssistiveLabel(AccessibleElement.PREVIOUS_MONTH,
+ "Navigate to previous month");
+ inlineDateField.setAssistiveLabel(AccessibleElement.NEXT_MONTH,
+ "Navigate to next month");
+ }));
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFields.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFields.java
index 5e8db6f14c..86bee4af69 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFields.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFields.java
@@ -5,12 +5,14 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
+import com.vaadin.annotations.Widgetset;
import com.vaadin.shared.ui.datefield.DateResolution;
import com.vaadin.tests.components.ComponentTestCase;
import com.vaadin.ui.Component;
import com.vaadin.ui.DateField;
@SuppressWarnings("serial")
+@Widgetset("com.vaadin.DefaultWidgetSet")
public class DateFields extends ComponentTestCase<DateField> {
private static final Locale[] LOCALES = { Locale.US, Locale.TAIWAN,
diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldAriaTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldAriaTest.java
new file mode 100644
index 0000000000..1d3cd39736
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldAriaTest.java
@@ -0,0 +1,59 @@
+package com.vaadin.tests.components.datefield;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.DateFieldElement;
+import com.vaadin.testbench.elements.InlineDateFieldElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class DateFieldAriaTest extends SingleBrowserTest {
+
+ @Test
+ public void changeAssistiveLabel() {
+ openTestURL();
+
+ DateFieldElement dateField = $(DateFieldElement.class).first();
+ dateField.openPopup();
+ WebElement prevMonthButton = driver
+ .findElement(By.className("v-datefield-popup"))
+ .findElement(By.className("v-button-prevmonth"));
+
+ Assert.assertEquals("Previous month",
+ prevMonthButton.getAttribute("aria-label"));
+
+ dateField.openPopup(); // This actually closes the calendar popup
+
+ ButtonElement changeLabelsButton = $(ButtonElement.class).first();
+ changeLabelsButton.click();
+
+ dateField.openPopup();
+ prevMonthButton = driver.findElement(By.className("v-datefield-popup"))
+ .findElement(By.className("v-button-prevmonth"));
+
+ Assert.assertEquals("Navigate to previous month",
+ prevMonthButton.getAttribute("aria-label"));
+ }
+
+ @Test
+ public void changeAssistiveLabelInline() {
+ openTestURL();
+
+ InlineDateFieldElement dateField = $(InlineDateFieldElement.class)
+ .first();
+ WebElement nextMonthElement = dateField
+ .findElement(By.className("v-button-nextmonth"));
+
+ Assert.assertEquals("Next month",
+ nextMonthElement.getAttribute("aria-label"));
+
+ ButtonElement changeLabelsButton = $(ButtonElement.class).first();
+ changeLabelsButton.click();
+
+ Assert.assertEquals("Navigate to next month",
+ nextMonthElement.getAttribute("aria-label"));
+ }
+}