Commit ac056272 authored by Jono Mingard's avatar Jono Mingard
Browse files

Use aria-describedby to link form errors to inputs (Bug #1273841)



New element_descriptors() method in pieforms to return a list of HTML IDs
which describe the given element (description, error or both) used by all
pieforms types which can logically use aria-describedby for some or all of
their elements.

Change-Id: Ie480b267f8c944dfe4e4bb6f185e8ce3ad8dd3ee
Signed-off-by: default avatarJono Mingard <jonom@catalyst.net.nz>
parent b90f215f
......@@ -58,7 +58,7 @@ function pieform_element_emaillist(Pieform $form, $element) {
$smarty->assign('disabled', !empty($element['disabled']));
if (isset($element['description'])) {
$smarty->assign('describedby', $form->get_name() . '_' . $element['id'] . '_description');
$smarty->assign('describedby', $form->element_descriptors($element));
}
return $smarty->fetch('form/emaillist.tpl');
......
......@@ -46,7 +46,7 @@ function pieform_element_tags(Pieform $form, $element) {
$smarty->assign('id', $form->get_name() . '_' . $element['id']);
$smarty->assign('value', join(', ', $value));
if (isset($element['description'])) {
$smarty->assign('describedby', $form->get_name() . '_' . $element['id'] . '_description');
$smarty->assign('describedby', $form->element_descriptors($element));
}
$smarty->left_delimiter = '{{';
......
......@@ -93,19 +93,19 @@ function pieform_renderer_maharatable(Pieform $form, $element) {
// Description - optional description of the element, or other note that should be visible
// on the form itself (without the user having to hover over contextual help
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['description'])) {
$result .= "\t<tr id=\"{$formname}_{$element['name']}_description\"";
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['descriptionhtml'])) {
$result .= "\t<tr";
if (isset($element['class'])) {
$result .= ' class="' . Pieform::hsc($element['class']) . '"';
}
$result .= ">\n\t\t<td class=\"description\">";
$result .= $element['description'];
$result .= $element['descriptionhtml'];
$result .= "</td>\n\t</tr>\n";
}
if (!empty($element['error'])) {
if (!empty($element['errorhtml'])) {
$result .= "\t<tr>\n\t\t<td class=\"errmsg\">";
$result .= hsc($element['error']);
$result .= hsc($element['errorhtml']);
$result .= "</td>\n\t</tr>\n";
}
......
......@@ -1106,9 +1106,12 @@ EOF;
foreach ($elementattributes as $attribute) {
if (isset($element[$attribute]) && $element[$attribute] !== '') {
if ($attribute == 'id') {
$element[$attribute] = $this->name . '_' . $element[$attribute];
$value = $this->name . '_' . $element[$attribute];
}
$result .= ' ' . $attribute . '="' . self::hsc($element[$attribute]) . '"';
else {
$value = $element[$attribute];
}
$result .= ' ' . $attribute . '="' . self::hsc($value) . '"';
}
}
......@@ -1116,14 +1119,14 @@ EOF;
$result .= ' title="' . self::hsc($element['elementtitle']) . '"';
}
if (isset($element['description'])) {
$result .= ' aria-describedby="' . $element['id'] . '_description"';
}
if (!in_array('maxlength', $exclude) && isset($element['rules']['maxlength'])) {
$result .= ' maxlength="' . intval($element['rules']['maxlength']) . '"';
}
if (!in_array('aria-describedby', $exclude)) {
$result .= ' aria-describedby="' . $this->element_descriptors($element) . '"';
}
foreach (array_diff(array('disabled', 'readonly'), $exclude) as $attribute) {
if (!empty($element[$attribute])) {
$result .= ($attribute == 'readonly') ? ' readonly="readonly" disabled="disabled"' : " $attribute=\"$attribute\"";
......@@ -1133,6 +1136,23 @@ EOF;
return $result;
}/*}}}*/
/**
* Returns a space-separated list of IDs of nodes which describe the given element
* Intended for use as the value of the aria-describedby attribute
*
* @param array $element The element to find descriptors for
*/
public function element_descriptors($element) {
$result = '';
if (!empty($element['error'])) {
$result .= $this->name . '_' . $element['id'] . '_error ';
}
if ((!$this->has_errors() || $this->get_property('showdescriptiononerror')) && !empty($element['description'])) {
$result .= $this->name . '_' . $element['id'] . '_description ';
}
return $result;
}
/**
* Includes a plugin file, checking any configured plugin directories.
*
......@@ -1417,6 +1437,19 @@ EOF;
}
}
// Element description
if (isset($element['description']) && $element['description'] !== '') {
$descriptionid = $this->name . '_' . $element['id'] . '_description';
$element['descriptionhtml'] = '<span id="' . $descriptionid . '">' . $element['description'] . '</span>';
}
// Error message
if (isset($element['error']) && $element['error'] !== '') {
$errorid = $this->name . '_' . $element['id'] . '_error';
$errortext = (!empty($element['isescaped'])) ? hsc($element['error']) : $element['error'];
$element['errorhtml'] = '<span id="' . $errorid . '">' . $errortext . '</span>';
}
// Help icon
if (!empty($element['help'])) {
$function = $this->get_property('helpcallback');
......
......@@ -70,13 +70,13 @@ function pieform_element_bytes(Pieform $form, $element) {/*{{{*/
$numberinput .= ' id="' . $formname . '_' . $name . '" value="' . Pieform::hsc($values['number']) . '" tabindex="' . Pieform::hsc($element['tabindex']) . '"';
$numberinput .= (isset($element['error']) ? ' class="error"' : '');
if (isset($element['description'])) {
$numberinput .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$numberinput .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$numberinput .= ">\n";
$uselect = '<select name="' . $name . '_units" id="' . $formname . '_' . $name . '_units"' . ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$uselect .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$uselect .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$uselect .= ">\n";
foreach (pieform_element_bytes_get_bytes_units() as $u) {
......
......@@ -48,7 +48,7 @@ function pieform_element_date(Pieform $form, $element) {/*{{{*/
. (!$required && !isset($element['defaultvalue']) ? ' disabled="disabled"' : '')
. ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$year .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$year .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$year .= ">\n";
for ($i = $element['minyear']; $i <= $element['maxyear']; $i++) {
......@@ -62,7 +62,7 @@ function pieform_element_date(Pieform $form, $element) {/*{{{*/
. (!$required && !isset($element['defaultvalue']) ? ' disabled="disabled"' : '')
. ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$month .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$month .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$month .= ">\n";
$monthnames = explode(',', $form->i18n('element', 'date', 'monthnames', $element));
......@@ -77,7 +77,7 @@ function pieform_element_date(Pieform $form, $element) {/*{{{*/
. (!$required && !isset($element['defaultvalue']) ? ' disabled="disabled"' : '')
. ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$day .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$day .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$day .= ">\n";
for ($i = 1; $i <= 31; $i++) {
......@@ -92,7 +92,7 @@ function pieform_element_date(Pieform $form, $element) {/*{{{*/
. (!$required && !isset($element['defaultvalue']) ? ' disabled="disabled"' : '')
. ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$hour .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$hour .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$hour .= ">\n";
for ($i = 0; $i <= 23; $i++) {
......@@ -106,7 +106,7 @@ function pieform_element_date(Pieform $form, $element) {/*{{{*/
. (!$required && !isset($element['defaultvalue']) ? ' disabled="disabled"' : '')
. ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$minute .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$minute .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$minute .= ">\n";
for ($i = 0; $i <= 59; $i++) {
......@@ -147,7 +147,7 @@ EOF;
. 'name="' . $name . '_optional" id="' . $name . '_optional" onchange="' . $name . '_toggle(this)" '
. 'tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$optional .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$optional .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$optional .= '>';
$optional .= ' <label for="' . $name . '_optional">' . $form->i18n('element', 'date', 'notspecified', $element);
......
......@@ -70,14 +70,14 @@ function pieform_element_expiry(Pieform $form, $element) {/*{{{*/
$numberinput .= ' type="text" size="4" name="' . $name . '"';
$numberinput .= ' id="' . $formname . '_' . $name . '" value="' . Pieform::hsc($values['number']) . '" tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$numberinput .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$numberinput .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$numberinput .= (isset($element['error']) ? ' class="error"' : '') . ">\n";
$uselect = '<select onchange="' . $name . '_change()" ';
$uselect .= 'name="' . $name . '_units" id="' . $formname . '_' . $name . '_units"' . ' tabindex="' . Pieform::hsc($element['tabindex']) . '"';
if (isset($element['description'])) {
$uselect .= ' aria-describedby="' . $form->get_name() . '_' . $element['id'] . '_description' . '"';
$uselect .= ' aria-describedby="' . $form->element_descriptors($element) . '"';
}
$uselect .= ">\n";
foreach (pieform_element_expire_get_expiry_units() as $u) {
......
......@@ -57,6 +57,7 @@ function pieform_element_radio(Pieform $form, $element) {
$i = 0;
foreach ($element['options'] as $value => $data) {
$idsuffix = substr(md5(microtime()), 0, 4);
$baseid = $element['id'];
$element['id'] = $uid = $id . $idsuffix;
if (is_array($data)) {
$text = $data['text'];
......@@ -67,7 +68,7 @@ function pieform_element_radio(Pieform $form, $element) {
$description = '';
}
$attributes = $form->element_attributes($element);
$attributes = preg_replace("/aria-describedby=\"([^\"]*){$idsuffix}_description\"/", 'aria-describedby="$1_description"', $attributes);
$attributes = preg_replace("/aria-describedby=\"[^\"]*{$baseid}{$idsuffix}_description\s*[^\"]*\"/", 'aria-describedby="$1_description"', $attributes);
$result .= '<input type="radio"'
. $attributes
. ' value="' . Pieform::hsc($value) . '"'
......
......@@ -64,12 +64,12 @@ function pieform_renderer_div(Pieform $form, $element) {/*{{{*/
// Description - optional description of the element, or other note that should be visible
// on the form itself (without the user having to hover over contextual help
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['description'])) {
$result .= '<div class="description"> ' . $element['description'] . "</div>";
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['descriptionhtml'])) {
$result .= '<div class="description"> ' . $element['descriptionhtml'] . "</div>";
}
if (!empty($element['error'])) {
$result .= '<div class="errmsg">' . ((!empty($element['isescaped'])) ? hsc($element['error']) : $element['error']) . '</div>';
$result .= '<div class="errmsg">' . $element['errorhtml'] . '</div>';
}
$result .= "</div>\n";
......
......@@ -90,9 +90,7 @@ function pieform_renderer_table(Pieform $form, $element) {/*{{{*/
// Description - optional description of the element, or other note that should be visible
// on the form itself (without the user having to hover over contextual help
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['description'])) {
$descriptionid = $form->get_name() . '_' . $element['id'] . '_description';
if ((!$form->has_errors() || $form->get_property('showdescriptiononerror')) && !empty($element['descriptionhtml'])) {
$result .= "\t<tr";
// Set the class of the enclosing <tr> to match that of the element
if (!empty($element['class'])) {
......@@ -100,18 +98,18 @@ function pieform_renderer_table(Pieform $form, $element) {/*{{{*/
}
$result .= ">\n\t\t";
if ($form->get_property('descriptionintwocells')) {
$result .= "<td></td><td class=\"description\" id=\"$descriptionid\">";
$result .= "<td></td><td class=\"description\">";
}
else {
$result .= "<td colspan=\"2\" class=\"description\" id=\"$descriptionid\">";
$result .= "<td colspan=\"2\" class=\"description\">";
}
$result .= $element['description'];
$result .= $element['descriptionhtml'];
$result .= "</td>\n\t</tr>\n";
}
if (!empty($element['error'])) {
if (!empty($element['errorhtml'])) {
$result .= "\t<tr>\n\t\t<td colspan=\"2\" class=\"errmsg\">";
$result .= (!empty($element['isescaped'])) ? hsc($element['error']) : $element['error'];
$result .= $element['errorhtml'];
$result .= "</td>\n\t</tr>\n";
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment