]> source.dussan.org Git - vaadin-framework.git/commitdiff
WAI-ARIA field corrections (#11407)
authormichaelvogt <michael@vaadin.com>
Mon, 25 Mar 2013 13:46:58 +0000 (15:46 +0200)
committerVaadin Code Review <review@vaadin.com>
Tue, 2 Apr 2013 14:10:58 +0000 (14:10 +0000)
Implementation of suggestions from usage test by an screen reader user

Change-Id: If02512f3d4ee60e3e115023af9d9e600dc11321a

15 files changed:
client/src/com/vaadin/client/VCaption.java
client/src/com/vaadin/client/ui/AriaHelper.java
client/src/com/vaadin/client/ui/HandlesAriaInvalid.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/HandlesAriaRequired.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCheckBox.java
client/src/com/vaadin/client/ui/VFilterSelect.java
client/src/com/vaadin/client/ui/VFormLayout.java
client/src/com/vaadin/client/ui/VOptionGroup.java
client/src/com/vaadin/client/ui/VPopupCalendar.java
client/src/com/vaadin/client/ui/VTextualDate.java
client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java
client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
server/src/com/vaadin/ui/PopupDateField.java
shared/src/com/vaadin/shared/ui/datefield/PopupDateFieldState.java

index 607a0f0b0ac33e2e774de89164c54a59b646bdd0..787b650f3f758bb7b09c9ad5fe88cc1ed5d334a1 100644 (file)
@@ -21,7 +21,6 @@ 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;
@@ -205,6 +204,8 @@ public class VCaption extends HTML {
             removeStyleDependentName("hasdescription");
         }
 
+        AriaHelper.handleInputRequired(owner.getWidget(), showRequired);
+
         if (showRequired) {
             if (requiredFieldIndicator == null) {
                 requiredFieldIndicator = DOM.createDiv();
@@ -215,9 +216,6 @@ public class VCaption extends HTML {
                 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);
@@ -226,11 +224,10 @@ public class VCaption extends HTML {
             // Remove existing
             DOM.removeChild(getElement(), requiredFieldIndicator);
             requiredFieldIndicator = null;
-
-            Roles.getTextboxRole().removeAriaRequiredProperty(
-                    owner.getWidget().getElement());
         }
 
+        AriaHelper.handleInputInvalid(owner.getWidget(), showError);
+
         if (showError) {
             if (errorIndicatorElement == null) {
                 errorIndicatorElement = DOM.createDiv();
@@ -240,6 +237,10 @@ public class VCaption extends HTML {
 
                 DOM.insertChild(getElement(), errorIndicatorElement,
                         getInsertPosition(InsertPosition.ERROR));
+
+                // Hide error indicator from assistive devices
+                Roles.getTextboxRole().setAriaHiddenState(
+                        errorIndicatorElement, true);
             }
         } else if (errorIndicatorElement != null) {
             // Remove existing
index 56f358f294dc221b1816a2f33f1f53c042841eb5..10038a720f48bf293338c8fdc723055ee49cd84d 100644 (file)
@@ -34,9 +34,9 @@ public class AriaHelper {
      * WAI-ARIA specification.
      * 
      * @param widget
-     *            Element, that should be bound to the caption
-     * @param captionElement
-     *            Element of the caption
+     *            Widget, that should be bound to the caption
+     * @param captionElements
+     *            Element with of caption to bind
      */
     public static void bindCaption(Widget widget, Element captionElement) {
         assert widget != null : "Valid Widget required";
@@ -62,44 +62,87 @@ public class AriaHelper {
         }
     }
 
-    public static void clearCaption(Widget widget) {
+    /**
+     * Removes a binding to a caption from the provided Widget.
+     * 
+     * @param widget
+     *            Widget, that was bound to a caption before
+     */
+    private static void clearCaption(Widget widget) {
         Roles.getTextboxRole()
                 .removeAriaLabelledbyProperty(widget.getElement());
     }
 
+    /**
+     * Handles the required actions depending of the input Widget being required
+     * or not.
+     * 
+     * @param widget
+     *            Widget, typically an input Widget like TextField
+     * @param required
+     *            boolean, true when the element is required
+     */
+    public static void handleInputRequired(Widget widget, boolean required) {
+        assert widget != null : "Valid Widget required";
+
+        if (widget instanceof HandlesAriaRequired) {
+            ((HandlesAriaRequired) widget).setRequired(required);
+        } else {
+            handleInputRequired(widget.getElement(), required);
+        }
+    }
+
     /**
      * Handles the required actions depending of the input element being
      * required or not.
      * 
-     * @param inputElement
-     *            Element, typically an input element
+     * @param element
+     *            Element, typically from an input Widget like TextField
      * @param required
      *            boolean, true when the element is required
      */
-    public static void handleInputRequired(Element inputElement,
-            boolean required) {
+    public static void handleInputRequired(Element element, boolean required) {
         if (required) {
-            Roles.getTextboxRole().setAriaRequiredProperty(inputElement, true);
+            Roles.getTextboxRole().setAriaRequiredProperty(element, required);
+        } else {
+            Roles.getTextboxRole().removeAriaRequiredProperty(element);
+        }
+    }
+
+    /**
+     * Handles the required actions depending of the input Widget contains
+     * unaccepted input.
+     * 
+     * @param widget
+     *            Widget, typically an input Widget like TextField
+     * @param invalid
+     *            boolean, true when the Widget input has an error
+     */
+    public static void handleInputInvalid(Widget widget, boolean invalid) {
+        assert widget != null : "Valid Widget required";
+
+        if (widget instanceof HandlesAriaInvalid) {
+            ((HandlesAriaInvalid) widget).setInvalid(invalid);
         } else {
-            Roles.getTextboxRole().removeAriaRequiredProperty(inputElement);
+            handleInputInvalid(widget.getElement(), invalid);
         }
     }
 
     /**
      * Handles the required actions depending of the input element contains
-     * unaccepted input
+     * unaccepted input.
      * 
-     * @param inputElement
-     *            Element, typically an input element
-     * @param showError
+     * @param element
+     *            Element, typically an input Widget like TextField
+     * @param invalid
      *            boolean, true when the element input has an error
      */
-    public static void handleInputError(Element inputElement, boolean showError) {
-        if (showError) {
-            Roles.getTextboxRole().setAriaInvalidState(inputElement,
+    public static void handleInputInvalid(Element element, boolean invalid) {
+        if (invalid) {
+            Roles.getTextboxRole().setAriaInvalidState(element,
                     InvalidValue.TRUE);
         } else {
-            Roles.getTextboxRole().removeAriaInvalidState(inputElement);
+            Roles.getTextboxRole().removeAriaInvalidState(element);
         }
     }
 
@@ -112,6 +155,8 @@ public class AriaHelper {
      * @return String with the id of the element
      */
     public static String ensureUniqueId(Element element) {
+        assert element != null : "Valid Element required";
+
         String id = element.getId();
         if (null == id || id.isEmpty()) {
             id = DOM.createUniqueId();
diff --git a/client/src/com/vaadin/client/ui/HandlesAriaInvalid.java b/client/src/com/vaadin/client/ui/HandlesAriaInvalid.java
new file mode 100644 (file)
index 0000000..1793c43
--- /dev/null
@@ -0,0 +1,17 @@
+package com.vaadin.client.ui;
+
+/**
+ * Some Widgets need to handle the required handling for WAI-ARIA themselfs, as
+ * this attribute needs to be set to the input element itself. In such a case,
+ * the Widget needs to implement this interface.
+ */
+public interface HandlesAriaInvalid {
+    /**
+     * Called to set the element, typically an input element, as invalid.
+     * 
+     * @param invalid
+     *            boolean, true when the element should be marked invalid, false
+     *            otherwise
+     */
+    void setInvalid(boolean invalid);
+}
diff --git a/client/src/com/vaadin/client/ui/HandlesAriaRequired.java b/client/src/com/vaadin/client/ui/HandlesAriaRequired.java
new file mode 100644 (file)
index 0000000..a2c6122
--- /dev/null
@@ -0,0 +1,16 @@
+package com.vaadin.client.ui;
+
+/**
+ * Some Widgets need to handle the required handling for WAI-ARIA themselfs, as
+ * this attribute needs to be set to the input element itself. In such a case,
+ * the Widget needs to implement this interface.
+ */
+public interface HandlesAriaRequired {
+    /**
+     * Called to set the element, typically an input element, as required.
+     * 
+     * @param required
+     *            boolean true when the element needs to be set as required
+     */
+    void setRequired(boolean required);
+}
index a94c2fcfee185c8a3762ae4f700db0ab8e5a9596..4c592d52a1f6eb356ecd36c80adab062952504d7 100644 (file)
@@ -16,8 +16,6 @@
 
 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;
@@ -26,7 +24,7 @@ import com.vaadin.client.Util;
 import com.vaadin.client.VTooltip;
 
 public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
-        Field {
+        Field, HandlesAriaInvalid, HandlesAriaRequired {
 
     public static final String CLASSNAME = "v-checkbox";
 
@@ -48,11 +46,6 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
     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,
@@ -76,22 +69,23 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
         }
     }
 
-    @Override
-    public void setValue(Boolean value, boolean fireEvents) {
-        setCheckedValue(value);
-        super.setValue(value, fireEvents);
+    /**
+     * Gives access to the input element.
+     * 
+     * @return Element of the CheckBox itself
+     */
+    private Element getCheckBoxElement() {
+        // FIXME: Would love to use a better way to access the checkbox element
+        return (Element) getElement().getFirstChildElement();
     }
 
-    private void setCheckedValue(Boolean value) {
-        CheckedValue checkedValue = value ? CheckedValue.TRUE
-                : CheckedValue.FALSE;
-        Roles.getCheckboxRole().setAriaCheckedState(getElement(), checkedValue);
+    @Override
+    public void setRequired(boolean required) {
+        AriaHelper.handleInputRequired(getCheckBoxElement(), required);
     }
 
     @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-
-        Roles.getCheckboxRole().setAriaDisabledState(getElement(), true);
+    public void setInvalid(boolean invalid) {
+        AriaHelper.handleInputInvalid(getCheckBoxElement(), invalid);
     }
 }
index 5025f272485ac11e9f367f52aa8f1f034897a189..d74bb6c7a203b548549ee86eb6e563715486776b 100644 (file)
@@ -82,7 +82,7 @@ import com.vaadin.shared.ui.combobox.FilteringMode;
 @SuppressWarnings("deprecation")
 public class VFilterSelect extends Composite implements Field, KeyDownHandler,
         KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
-        SubPartAware {
+        SubPartAware, HandlesAriaInvalid, HandlesAriaRequired {
 
     /**
      * Represents a suggestion in the suggestion popup box
@@ -1053,6 +1053,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
         });
 
         popupOpener.sinkEvents(Event.ONMOUSEDOWN);
+        Roles.getButtonRole()
+                .setAriaHiddenState(popupOpener.getElement(), true);
         Roles.getButtonRole().set(popupOpener.getElement());
 
         panel.add(tb);
@@ -1831,4 +1833,14 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
         }
         return null;
     }
+
+    @Override
+    public void setRequired(boolean required) {
+        AriaHelper.handleInputRequired(tb, required);
+    }
+
+    @Override
+    public void setInvalid(boolean invalid) {
+        AriaHelper.handleInputInvalid(tb, invalid);
+    }
 }
index 4c7190340f76e6d0215de35fcc7c2957983042db..37a2541aba194825231a93985ea91d27e275d8ab 100644 (file)
@@ -302,6 +302,9 @@ public class VFormLayout extends SimplePanel {
 
             boolean required = owner instanceof AbstractFieldConnector
                     && ((AbstractFieldConnector) owner).isRequired();
+
+            AriaHelper.handleInputRequired(owner.getWidget(), required);
+
             if (required) {
                 if (requiredFieldIndicator == null) {
                     requiredFieldIndicator = DOM.createSpan();
@@ -310,9 +313,6 @@ public class VFormLayout extends SimplePanel {
                             "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(
@@ -322,9 +322,6 @@ public class VFormLayout extends SimplePanel {
                 if (requiredFieldIndicator != null) {
                     DOM.removeChild(getElement(), requiredFieldIndicator);
                     requiredFieldIndicator = null;
-
-                    Roles.getTextboxRole().removeAriaRequiredProperty(
-                            owner.getWidget().getElement());
                 }
             }
 
@@ -379,6 +376,8 @@ public class VFormLayout extends SimplePanel {
                 showError = false;
             }
 
+            AriaHelper.handleInputInvalid(owner.getWidget(), showError);
+
             if (showError) {
                 if (errorIndicatorElement == null) {
                     errorIndicatorElement = DOM.createDiv();
@@ -386,6 +385,11 @@ public class VFormLayout extends SimplePanel {
                     DOM.setElementProperty(errorIndicatorElement, "className",
                             "v-errorindicator");
                     DOM.appendChild(getElement(), errorIndicatorElement);
+
+                    // Hide the error indicator from screen reader, as this
+                    // information is set directly at the input field
+                    Roles.getFormRole().setAriaHiddenState(
+                            errorIndicatorElement, true);
                 }
 
             } else if (errorIndicatorElement != null) {
index f0c7b7f83d44e2d41a2ba4f1300ea7196a6314a2..eed5549e39adb9112bb73bfeda58ae834b3244de 100644 (file)
@@ -22,8 +22,6 @@ import java.util.Iterator;
 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;
@@ -35,7 +33,6 @@ import com.google.gwt.event.dom.client.LoadEvent;
 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;
@@ -50,7 +47,7 @@ import com.vaadin.shared.EventId;
 import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
 
 public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
-        BlurHandler, HandlesAriaCaption {
+        BlurHandler {
 
     public static final String CLASSNAME = "v-select-optiongroup";
 
@@ -89,8 +86,6 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
     /** 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;
@@ -131,30 +126,9 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
             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) {
@@ -199,13 +173,6 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
             }
             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);
-            }
         }
     }
 
@@ -279,22 +246,4 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
             });
         }
     }
-
-    @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());
-        }
-    }
 }
index a437f3c64a1360403083f3c2579361ecc4184f41..aa34a1b4e362cedde47300d4c72c5cce3585e719 100644 (file)
@@ -44,6 +44,7 @@ import com.vaadin.client.BrowserInfo;
 import com.vaadin.client.VConsole;
 import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
 import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.shared.ui.datefield.PopupDateFieldState;
 import com.vaadin.shared.ui.datefield.Resolution;
 
 /**
@@ -79,6 +80,8 @@ public class VPopupCalendar extends VTextualDate implements Field,
 
     private Label selectedDate;
 
+    private Element descriptionForAssisitveDevicesElement;
+
     public VPopupCalendar() {
         super();
 
@@ -93,6 +96,16 @@ public class VPopupCalendar extends VTextualDate implements Field,
 
         add(calendarToggle);
 
+        // Description of the usage of the widget for assisitve device users
+        descriptionForAssisitveDevicesElement = DOM.createDiv();
+        descriptionForAssisitveDevicesElement
+                .setInnerText(PopupDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES);
+        AriaHelper.ensureUniqueId(descriptionForAssisitveDevicesElement);
+        Id.of(descriptionForAssisitveDevicesElement);
+        AriaHelper
+                .visibleForAssistiveDevicesOnly(descriptionForAssisitveDevicesElement);
+        DOM.appendChild(getElement(), descriptionForAssisitveDevicesElement);
+
         calendar = GWT.create(VCalendarPanel.class);
         calendar.setParentField(this);
         calendar.setFocusOutListener(new FocusOutListener() {
@@ -265,7 +278,7 @@ public class VPopupCalendar extends VTextualDate implements Field,
         if (isTextFieldEnabled()) {
             super.clearAriaCaption();
         } else {
-            AriaHelper.clearCaption(calendarToggle);
+            AriaHelper.bindCaption(calendarToggle, null);
         }
 
         handleAriaAttributes();
@@ -546,4 +559,26 @@ public class VPopupCalendar extends VTextualDate implements Field,
         return super.getSubPartName(subElement);
     }
 
+    /**
+     * Set a description that explains the usage of the Widget for users of
+     * assistive devices.
+     * 
+     * @param descriptionForAssistiveDevices
+     *            String with the description
+     */
+    public void setDescriptionForAssistiveDevices(
+            String descriptionForAssistiveDevices) {
+        descriptionForAssisitveDevicesElement
+                .setInnerText(descriptionForAssistiveDevices);
+    }
+
+    /**
+     * Get the description that explains the usage of the Widget for users of
+     * assistive devices.
+     * 
+     * @return String with the description
+     */
+    public String getDescriptionForAssistiveDevices() {
+        return descriptionForAssisitveDevicesElement.getInnerText();
+    }
 }
index 97a868b69d8e4108a57c5cbda4cd2ac75ddc58bd..92cc81605c6902856fa5a1a8730359ab9928b460 100644 (file)
@@ -35,7 +35,8 @@ import com.vaadin.shared.EventId;
 import com.vaadin.shared.ui.datefield.Resolution;
 
 public class VTextualDate extends VDateField implements Field, ChangeHandler,
-        Focusable, SubPartAware, HandlesAriaCaption {
+        Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
+        HandlesAriaRequired {
 
     private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
 
@@ -161,7 +162,17 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
 
     @Override
     public void clearAriaCaption() {
-        AriaHelper.clearCaption(text);
+        AriaHelper.bindCaption(text, null);
+    }
+
+    @Override
+    public void setRequired(boolean required) {
+        AriaHelper.handleInputRequired(text, required);
+    }
+
+    @Override
+    public void setInvalid(boolean invalid) {
+        AriaHelper.handleInputInvalid(text, invalid);
     }
 
     /**
index 772419e73072330208e043c18c6c124d50e07f0a..ebfda2d71579d16bd7d7f9bf3e6bf3ef980492ab 100644 (file)
@@ -69,6 +69,8 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
                 blurHandlerRegistration);
 
         if (null != getState().errorMessage) {
+            getWidget().setInvalid(true);
+
             if (getWidget().errorIndicatorElement == null) {
                 getWidget().errorIndicatorElement = DOM.createSpan();
                 getWidget().errorIndicatorElement.setInnerHTML("&nbsp;");
@@ -85,12 +87,13 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
         } else if (getWidget().errorIndicatorElement != null) {
             DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display",
                     "none");
-        }
 
-        if (isReadOnly()) {
-            getWidget().setEnabled(false);
+            getWidget().setInvalid(false);
         }
 
+        getWidget().setRequired(isRequired());
+        getWidget().setEnabled(!isReadOnly());
+
         if (getIcon() != null) {
             if (getWidget().icon == null) {
                 getWidget().icon = new Icon(getConnection());
index 7246c27b6b18900b0c6016ceb43c8eb69ce270ce..f59ac713e8478ca5cb018e8025641b94bff6ae60 100644 (file)
@@ -119,6 +119,8 @@ public class PopupDateFieldConnector extends TextualDateConnector {
                     + "-button-readonly");
         }
 
+        getWidget().setDescriptionForAssistiveDevices(
+                getState().descriptionForAssistiveDevices);
         getWidget().calendarToggle.setEnabled(true);
     }
 
index ac5a08475eed9f754241a4aa31004bd42f0d4160..9cda9951600a109957f556d1527ded27abc652bb 100644 (file)
@@ -30,8 +30,8 @@ import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractFieldConnector;
 import com.vaadin.client.ui.AbstractLayoutConnector;
-import com.vaadin.client.ui.LayoutClickEventHandler;
 import com.vaadin.client.ui.AriaHelper;
+import com.vaadin.client.ui.LayoutClickEventHandler;
 import com.vaadin.client.ui.layout.ElementResizeEvent;
 import com.vaadin.client.ui.layout.ElementResizeListener;
 import com.vaadin.shared.AbstractFieldState;
@@ -259,10 +259,8 @@ public abstract class AbstractOrderedLayoutConnector extends
         slot.setCaption(caption, iconUrlString, styles, error, showError,
                 required, enabled);
 
-        AriaHelper.handleInputRequired(child.getWidget().getElement(),
-                required);
-        AriaHelper.handleInputError(child.getWidget().getElement(),
-                showError);
+        AriaHelper.handleInputRequired(child.getWidget(), required);
+        AriaHelper.handleInputInvalid(child.getWidget(), showError);
         AriaHelper.bindCaption(child.getWidget(), slot.getCaptionElement());
 
         if (slot.hasCaption()) {
index ae33493c894f892a8bd53287b7badfc714afd235..b113378e74250805d0d1ecc9caa56861cb491802 100644 (file)
@@ -118,4 +118,24 @@ public class PopupDateField extends DateField {
         getState().textFieldEnabled = state;
     }
 
+    /**
+     * Set a description that explains the usage of the Widget for users of
+     * assistive devices.
+     * 
+     * @param descriptionForAssistiveDevices
+     *            String with the description
+     */
+    public void setDescriptionForAssistiveDevices(String description) {
+        getState().descriptionForAssistiveDevices = description;
+    }
+
+    /**
+     * Get the description that explains the usage of the Widget for users of
+     * assistive devices.
+     * 
+     * @return String with the description
+     */
+    public String getDescriptionForAssistiveDevices() {
+        return getState().descriptionForAssistiveDevices;
+    }
 }
index 74cab3efb01fd787e61f381f6115e5758917148e..1c061b3ac37a30c902324c0d6155209fde3b4e07 100644 (file)
 package com.vaadin.shared.ui.datefield;
 
 public class PopupDateFieldState extends TextualDateFieldState {
+    public static final String DESCRIPTION_FOR_ASSISTIVE_DEVICES = "Arrow down key opens calendar element for choosing the date";
+
     {
         primaryStyleName = "v-datefield";
     }
 
     public boolean textFieldEnabled = true;
-
+    public String descriptionForAssistiveDevices = DESCRIPTION_FOR_ASSISTIVE_DEVICES;
 }