Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve accessibility for checkboxes #2874

Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bfaa2cc
improve accessibility for checkboxes
elsenhans May 8, 2023
8d33a91
improve accessibility for checkboxes in holds/list.phtml
elsenhans May 9, 2023
1da832d
add translations for improve accessibility for checkboxes in account …
elsenhans May 9, 2023
ad01c49
change order of translations
elsenhans May 9, 2023
c2a2d52
change clipboard to book bag; add missing transEscAttr
elsenhans May 9, 2023
fb426c2
change recall to recalls
elsenhans May 9, 2023
7500636
remove with_selected language token
elsenhans May 10, 2023
2cc90c4
add new language token select_all_on_page; change entries to items
elsenhans May 10, 2023
bf813bf
fix typo to: items on the page
elsenhans May 10, 2023
04eacd7
Update en.ini
demiankatz May 10, 2023
03b20d5
use select_all_on_page in checkedout.phtml and list.phtml
elsenhans May 11, 2023
5c0e6d3
renaming of view helper method: getTitleDescribedById => getUniqueIdW…
RLangeUni May 12, 2023
714004a
use escapeHtmlAttr() for $describedById and $cartId
RLangeUni May 12, 2023
c42074d
cart: add id in contents to reference from cart-buttons
RLangeUni May 12, 2023
f85803a
uniqueId: use getUniqueIdWithSource() consistently in lists and toolbar
RLangeUni May 12, 2023
f034831
uniqueId: use getUniqueIdWithSource() in record view helper getCheckbox
RLangeUni May 12, 2023
96e0692
change translations for select_item_hold_cancel
elsenhans May 16, 2023
6fb9a63
rename method for id with source and introduce new for templates to g…
RLangeUni May 17, 2023
e8e6c79
undo changes for cart described by id (conflicts with js?)
RLangeUni May 17, 2023
6b2ad39
use prefix for lightbox
RLangeUni May 18, 2023
597cd7a
add prefix for cart content
RLangeUni May 25, 2023
db3f73e
add prefix for cart content
RLangeUni May 25, 2023
8957ff6
add prefix for cart content - fix multiline
RLangeUni May 25, 2023
84ab077
add context as prefix for id
RLangeUni Jun 1, 2023
b5fb3d9
set context as string in templates
RLangeUni Jun 1, 2023
dd7a49f
move generation of id to record view helper
RLangeUni Jun 1, 2023
b954a76
adapt record test for prefixing
RLangeUni Jun 1, 2023
520429b
fix code styles
RLangeUni Jun 1, 2023
ad2062b
fix code styles
RLangeUni Jun 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion languages/de.ini
Expand Up @@ -1184,7 +1184,12 @@ See also = "Siehe auch"
see_all_ellipsis = "Alle anzeigen …"
Select this record = "Datensatz auswählen"
Select your carrier = "Wählen Sie Ihren Telefonanbieter aus"
select_page = "Alles auswählen"
select_item_checked_out_renew = "Titel zum Verlängern auswählen"
select_item_hold_cancel = "Titel auswählen, um Bestellung oder Vormerkung zu stornieren"
select_item_ill_request_cancel = "Titel auswählen, um Fernleihanfrage zu stornieren"
select_item_storage_retrieval_request_cancel = "Titel auswählen, um Magazinbestellung zu stornieren"
select_page = "Alle Einträge der Seite auswählen"
select_page_cart = "Alle Einträge der Zwischenablage auswählen"
select_pickup_location = "Abholort auswählen"
select_request_group = "Abholstelle auswählen"
Selected = "Ausgewählt"
Expand Down
7 changes: 6 additions & 1 deletion languages/en.ini
Expand Up @@ -1184,7 +1184,12 @@ See also = "See also"
see_all_ellipsis = "see all…"
Select this record = "Select this record"
Select your carrier = "Select your carrier"
select_page = "Select Page"
select_item_checked_out_renew = "Select item for renewing"
select_item_hold_cancel = "Select item for canceling holds or recalls"
elsenhans marked this conversation as resolved.
Show resolved Hide resolved
select_item_ill_request_cancel = "Select item for canceling interlibrary loan requests"
select_item_storage_retrieval_request_cancel = "Select item for canceling storage retrieval requests"
select_page = "Select all entries of the page"
select_page_cart = "Select all entries of the Book Bag"
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
select_pickup_location = "Select Pick Up Location"
select_request_group = "Select Request Group"
Selected = "Selected"
Expand Down
10 changes: 10 additions & 0 deletions module/VuFind/src/VuFind/View/Helper/Root/Record.php
Expand Up @@ -707,4 +707,14 @@ protected function deduplicateLinks($links)
{
return array_values(array_unique($links, SORT_REGULAR));
}

/**
* Get the unique title id of the record
*
* @return string
*/
public function getTitleDescribedById()
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
{
return "{$this->driver->getSourceIdentifier()}|{$this->driver->getUniqueId()}";
}
}
Expand Up @@ -14,6 +14,7 @@
$cover = $coverDetails['html'];
$thumbnail = false;
$thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('list');
$describedById = $this->record($this->driver)->getTitleDescribedById();
?>
<li class="result<?php if ($this->driver->supportsAjaxStatus()): ?> ajaxItem<?php endif ?>">
<?php if ($cover):
Expand All @@ -36,7 +37,7 @@
<div class="resultItemLine1">
<?php $missing = $this->driver instanceof \VuFind\RecordDriver\Missing; ?>
<?php if (!$missing): ?><a href="<?=$this->escapeHtmlAttr($this->recordLinker()->getUrl($this->driver))?>" class="getFull" data-view="<?=$this->params->getOptions()->getListViewOption() ?>"><?php endif; ?>
<span class="title"><?=$this->record($this->driver)->getTitleHtml()?></span>
<span id="<?=$describedById?>" class="title"><?=$this->record($this->driver)->getTitleHtml()?></span>
<?php if (!$missing): ?></a><?php endif; ?>
<?php foreach ($this->driver->tryMethod('getTitlesAltScript', [], []) as $altTitle): ?>
<div class="title-alt">
Expand Down
Expand Up @@ -4,6 +4,7 @@
$cover = $coverDetails['html'];
$thumbnail = false;
$thumbnailAlignment = $this->record($this->driver)->getThumbnailAlignment('result');
$describedById = $this->record($this->driver)->getTitleDescribedById();
if ($cover):
ob_start(); ?>
<div class="media-<?=$thumbnailAlignment ?> <?=$this->escapeHtmlAttr($coverDetails['size'])?>">
Expand All @@ -21,7 +22,7 @@
<div class="media-body">
<div class="result-body">
<div>
<a href="<?=$this->escapeHtmlAttr($recordLinker->getUrl($this->driver))?>" class="title getFull" data-view="<?=isset($this->params) ? $this->params->getOptions()->getListViewOption() : 'list' ?>">
<a id="<?=$describedById?>" href="<?=$this->escapeHtmlAttr($recordLinker->getUrl($this->driver))?>" class="title getFull" data-view="<?=isset($this->params) ? $this->params->getOptions()->getListViewOption() : 'list' ?>">
<?=$this->record($this->driver)->getTitleHtml()?>
</a>
<?php foreach ($this->driver->tryMethod('getTitlesAltScript', [], []) as $altTitle): ?>
Expand Down Expand Up @@ -179,7 +180,7 @@
<?php if ($this->userlist()->getMode() !== 'disabled'): ?>
<?php if ($this->permission()->allowDisplay('feature.Favorites')): ?>
<?php /* Add to favorites */ ?>
<a href="<?=$this->escapeHtmlAttr($recordLinker->getActionUrl($this->driver, 'Save'))?>" data-lightbox class="save-record result-link icon-link" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId()) ?>">
<a href="<?=$this->escapeHtmlAttr($recordLinker->getActionUrl($this->driver, 'Save'))?>" data-lightbox class="save-record result-link icon-link" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId()) ?>" aria-describedby="<?=$describedById?>">
<?=$this->icon('user-favorites', 'icon-link__icon') ?>
<span class="result-link-label icon-link__label"><?=$this->transEsc('Add to favorites')?></span>
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
</a><br>
Expand All @@ -197,7 +198,7 @@
<?php foreach ($trees as $hierarchyID => $hierarchyTitle): ?>
<div class="hierarchyTreeLink">
<input type="hidden" value="<?=$this->escapeHtmlAttr($hierarchyID)?>" class="hiddenHierarchyId">
<a class="hierarchyTreeLinkText result-link-label icon-link" data-lightbox href="<?=$this->escapeHtmlAttr($recordLinker->getTabUrl($this->driver, 'HierarchyTree', ['hierarchy' => $hierarchyID]))?>#tabnav" title="<?=$this->transEscAttr('hierarchy_tree')?>" data-lightbox-href="<?=$this->escapeHtmlAttr($recordLinker->getTabUrl($this->driver, 'AjaxTab', ['hierarchy' => $hierarchyID]))?>" data-lightbox-post="tab=hierarchytree">
<a class="hierarchyTreeLinkText result-link-label icon-link" data-lightbox href="<?=$this->escapeHtmlAttr($recordLinker->getTabUrl($this->driver, 'HierarchyTree', ['hierarchy' => $hierarchyID]))?>#tabnav" title="<?=$this->transEscAttr('hierarchy_tree')?>" data-lightbox-href="<?=$this->escapeHtmlAttr($recordLinker->getTabUrl($this->driver, 'AjaxTab', ['hierarchy' => $hierarchyID]))?>" data-lightbox-post="tab=hierarchytree" aria-describedby="<?=$describedById?>">
<?=$this->icon('tree-context', 'icon-link__icon') ?>
<span class="icon-link__label"><?=$this->transEsc('hierarchy_view_context')?><?php if (count($trees) > 1): ?>: <?=$this->escapeHtml($hierarchyTitle)?><?php endif; ?></span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion themes/bootstrap3/templates/cart/cart.phtml
Expand Up @@ -14,7 +14,7 @@
<div class="checkbox pull-left flip">
<label>
<input type="checkbox" name="selectAll" class="checkbox-select-all">
<?=$this->transEsc('select_page')?>
<?=$this->transEsc('select_page_cart')?>
</label>
</div>
<?php if ($this->userlist()->getMode() !== 'disabled'): ?>
Expand Down
7 changes: 4 additions & 3 deletions themes/bootstrap3/templates/holds/list.phtml
Expand Up @@ -62,6 +62,7 @@
<?php foreach ($this->recordList as $resource): ?>
<?php $iteration++; ?>
<?php $ilsDetails = $resource->getExtraDetail('ils_details'); ?>
<?php $describedById = $this->record($resource)->getTitleDescribedById(); ?>
<li class="result">
<?php if (($this->cancelForm && isset($ilsDetails['cancel_details'])) || ($this->updateForm && isset($ilsDetails['updateDetails']))): ?>
<?php $safeId = preg_replace('/[^a-zA-Z0-9]/', '', $ilsDetails['cancel_details'] ?? $ilsDetails['updateDetails']); ?>
Expand All @@ -70,7 +71,7 @@
<?php endif; ?>
<div class="checkbox">
<label>
<input type="checkbox" name="selectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details'] ?? $ilsDetails['updateDetails']) ?>" id="checkbox_<?=$safeId?>" class="checkbox-select-item">
<input type="checkbox" name="selectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details'] ?? $ilsDetails['updateDetails']) ?>" id="checkbox_<?=$safeId?>" class="checkbox-select-item" aria-describedby="<?=$describedById?>" aria-label="<?=$this->transEscAttr('select_item_hold_cancel')?>">
</label>
</div>
<?php elseif ($this->cancelForm || $this->updateForm): ?>
Expand Down Expand Up @@ -103,11 +104,11 @@
if (is_a($resource, 'VuFind\\RecordDriver\\SolrDefault') && !is_a($resource, 'VuFind\\RecordDriver\\Missing')) {
$title = $resource->getTitle();
$title = empty($title) ? $this->transEsc('Title not available') : $this->escapeHtml($title);
echo '<a href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
echo '<a id="' . $describedById . '" href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
. '" class="title">' . $title . '</a>';
} elseif (isset($ilsDetails['title']) && !empty($ilsDetails['title'])) {
// If the record is not available in Solr, perhaps the ILS driver sent us a title we can show...
echo '<span class="title">' . $this->escapeHtml($ilsDetails['title']) . '</span>';
echo '<span class="title" id="' . $describedById . '">' . $this->escapeHtml($ilsDetails['title']) . '</span>';
} else {
// Last resort -- indicate that no title could be found.
echo $this->transEsc('Title not available');
Expand Down
Expand Up @@ -6,7 +6,7 @@
<nav class="bulkActionButtons">
<div class="bulk-checkbox">
<input type="checkbox" name="selectAll" class="checkbox-select-all" id="myresearchCheckAll">
<label for="myresearchCheckAll"><?=$this->transEsc('select_page')?> | <?=$this->transEsc('with_selected')?>:</label>
<label for="myresearchCheckAll"><?=$this->transEsc('select_page')?>:</label>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you removing the with_selected text? I think the purpose of this is to indicate that the buttons in this region only impact selected items. I admit that this may not be the most clear way to accomplish that clarification, but I'm not sure if completely removing it makes things better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the label ("| Selection:") is misleading because it refers to the following buttons/checkboxes. It does not refer to the checkbox where the label is. That's why this part of the caption should be removed from the label element.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, but should we just move it outside of the label tag rather than deleting it entirely?

Copy link
Contributor Author

@elsenhans elsenhans May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the label consisted of 2 parts:
"Select Page | with selected" (or with tokens: select_page | with_selected)
Now the label says:
"Select all entries of the page" (or with tokens: select_page)

  • the with_selected part is misleading, so we removed that
  • also we have adjusted the translation for the part of the label which is still there: select_page = "Select all entries of the page"

Does that make sense for you @demiankatz ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "with_selected" part is misleading when it's included as part of the label for the "select all" checkbox, but I think it is helpful to provide context for the buttons. I'm just suggesting you change:

<checkbox><label>select_page | with_selected</label> <buttons>

to:

<checkbox><label>select_page</label> | with_selected <buttons>

Also, I think "Select all entries of the page" seems very verbose for a short inline checkbox -- we chose "Select Page" to be as concise as possible. Maybe there's a middle ground that's more clear but also shorter. We can't use "Select All" because that might imply "all items in result set" and we need to be clear that it's only "all items on current page." But maybe "Select All on Page" would be a happier middle ground?

Also note that significantly changing an existing translation string can be problematic, because it won't automatically trigger translations of all the other languages. If we really do want to significantly change this label, we might want to rename it as well to ensure that appropriate matching translations are created (e.g. select_all_on_page instead of select_page).

Copy link
Contributor

@crhallberg crhallberg May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that "with_selected" and misleading and may be poorly translated. I agree that @elsenhans's "Select all entries" is a better label.

EDIT: DK helped me see that that "with_selected" is for the buttons and not the checkbox. It does need to stay but maybe wrapping the buttons in an element like a div would make the separation more clear for future development.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crhallberg, @sturkel89 and I just looked at some other examples of controls similar to these in different sites. It does seem like we can safely drop the "with_selected" part, since it doesn't really add much clarity. If we get rid of it in both places where it is used, we should also remove it from the language files -- you can do it automatically with php $VUFIND_HOME/public/index.php language/delete with_selected if you want to.

I think the relationship between the select boxes and the buttons may be more clear if the buttons are disabled when no boxes are checked. That is probably out of scope for this PR, but it might be something we should consider as a follow-up.

Some systems display a message after or below the buttons indicating how many items are selected (e.g. "You have selected X items."). That might be a useful addition. (Again, possibly a follow-up PR rather than part of this one).

In situations where results are numbered, using those numbers for the select all label might be more clear and concise than the other options we have discussed -- e.g. "Select 1-20." However, not every listing in VuFind is numbered, so this idea may have limited applications. Maybe we should consider adding more numbers to help accommodate it. If we can't, though, I at least have some further suggestions for refining your proposed language strings, which I will propose above.

What do you think, @elsenhans?

Copy link
Contributor Author

@elsenhans elsenhans May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @demiankatz I did remove "with_selected" from the language files.

To disable the buttons if no checkbox is selected sounds good to me.
With the additional text "You have selected X items" and numbering the results, I am not sure if that would really be helpful. Numbers don't have any connection or information to the record and that's why they do not contain added value. @ckaz what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the language file update. I think you may have meant to tag @ckaz here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckaz Do you have an opinion on this:
"Some systems display a message after or below the buttons indicating how many items are selected (e.g. "You have selected X items."). That might be a useful addition. (Again, possibly a follow-up PR rather than part of this one).
In situations where results are numbered, using those numbers for the select all label might be more clear and concise than the other options we have discussed -- e.g. "Select 1-20." However, not every listing in VuFind is numbered, so this idea may have limited applications. Maybe we should consider adding more numbers to help accommodate it."

</div>
<ul class="action-toolbar">
<li><button class="toolbar-btn" type="submit" name="email" value="1" title="<?=$this->transEscAttr('email_selected')?>"><?=$this->icon('send-email') ?> <?=$this->transEsc('Email') ?></button>
Expand Down
7 changes: 4 additions & 3 deletions themes/bootstrap3/templates/myresearch/checkedout.phtml
Expand Up @@ -93,13 +93,14 @@
<ul class="record-list">
<?php $i = 0; foreach ($this->transactions as $resource): ?>
<?php $ilsDetails = $resource->getExtraDetail('ils_details'); ?>
<?php $describedById = $this->record($resource)->getTitleDescribedById(); ?>
<li id="record<?=$this->escapeHtmlAttr($resource->getUniqueId())?>" class="result">
<?php if ($this->renewForm): ?>
<div class="checkbox">
<?php if (isset($ilsDetails['renewable']) && $ilsDetails['renewable'] && isset($ilsDetails['renew_details'])): ?>
<?php $safeId = preg_replace('/[^a-zA-Z0-9]/', '', $ilsDetails['renew_details']); ?>
<label>
<input class="checkbox-select-item" type="checkbox" name="renewSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['renew_details'])?>" id="checkbox_<?=$safeId?>">
<input class="checkbox-select-item" type="checkbox" name="renewSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['renew_details'])?>" id="checkbox_<?=$safeId?>" aria-describedby="<?=$describedById?>" aria-label="<?=$this->transEscAttr('select_item_checked_out_renew')?>">
</label>
<input type="hidden" name="selectAllIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['renew_details'])?>">
<input type="hidden" name="renewAllIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['renew_details'])?>">
Expand Down Expand Up @@ -132,11 +133,11 @@
if (is_a($resource, 'VuFind\\RecordDriver\\SolrDefault') && !is_a($resource, 'VuFind\\RecordDriver\\Missing')) {
$title = $resource->getTitle();
$title = empty($title) ? $this->transEsc('Title not available') : $this->escapeHtml($title);
echo '<a href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource)) .
echo '<a id="' . $describedById . '" href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource)) .
'" class="title">' . $title . '</a>';
} elseif (isset($ilsDetails['title']) && !empty($ilsDetails['title'])) {
// If the record is not available in Solr, perhaps the ILS driver sent us a title we can show...
echo $this->escapeHtml($ilsDetails['title']);
echo '<span id="' . $describedById . '">' . $this->escapeHtml($ilsDetails['title']) . '</span>';
} else {
// Last resort -- indicate that no title could be found.
echo $this->transEsc('Title not available');
Expand Down
7 changes: 4 additions & 3 deletions themes/bootstrap3/templates/myresearch/illrequests.phtml
Expand Up @@ -48,13 +48,14 @@
<?php foreach ($this->recordList as $resource): ?>
<?php $iteration++; ?>
<?php $ilsDetails = $resource->getExtraDetail('ils_details'); ?>
<?php $describedById = $this->record($resource)->getTitleDescribedById(); ?>
<li id="record<?=$this->escapeHtmlAttr($resource->getUniqueId()) ?>" class="result">
<?php if ($this->cancelForm && isset($ilsDetails['cancel_details'])): ?>
<?php $safeId = preg_replace('/[^a-zA-Z0-9]/', '', $resource->getUniqueId()); ?>
<div class="checkbox">
<input type="hidden" name="cancelAllIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>">
<label>
<input type="checkbox" name="cancelSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>" id="checkbox_<?=$safeId?>">
<input type="checkbox" name="cancelSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>" id="checkbox_<?=$safeId?>" aria-describedby="<?=$describedById?>" aria-label="<?=$this->transEscAttr('select_item_ill_request_cancel')?>">
</label>
</div>
<?php endif; ?>
Expand Down Expand Up @@ -82,11 +83,11 @@
if (is_a($resource, 'VuFind\\RecordDriver\\SolrDefault') && !is_a($resource, 'VuFind\\RecordDriver\\Missing')) {
$title = $resource->getTitle();
$title = empty($title) ? $this->transEsc('Title not available') : $this->escapeHtml($title);
echo '<a href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
echo '<a id="' . $describedById . '" href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
. '" class="title">' . $title . '</a>';
} elseif (isset($ilsDetails['title']) && !empty($ilsDetails['title'])) {
// If the record is not available in Solr, perhaps the ILS driver sent us a title we can show...
echo $this->escapeHtml($ilsDetails['title']);
echo '<span id="' . $describedById . '">' . $this->escapeHtml($ilsDetails['title']) . '</span>';
} else {
// Last resort -- indicate that no title could be found.
echo $this->transEsc('Title not available');
Expand Down
Expand Up @@ -47,13 +47,14 @@
<?php foreach ($this->recordList as $resource): ?>
<?php $iteration++; ?>
<?php $ilsDetails = $resource->getExtraDetail('ils_details'); ?>
<?php $describedById = $this->record($resource)->getTitleDescribedById(); ?>
<li id="record<?=$this->escapeHtmlAttr($resource->getUniqueId()) ?>" class="result">
<?php if ($this->cancelForm && isset($ilsDetails['cancel_details'])): ?>
<?php $safeId = preg_replace('/[^a-zA-Z0-9]/', '', $resource->getUniqueId()); ?>
<div class="checkbox">
<input type="hidden" name="cancelAllIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>">
<label class="pull-left flip">
<input type="checkbox" name="cancelSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>" id="checkbox_<?=$safeId?>">
<input type="checkbox" name="cancelSelectedIDS[]" value="<?=$this->escapeHtmlAttr($ilsDetails['cancel_details']) ?>" id="checkbox_<?=$safeId?>" aria-describedby="<?=$describedById?>" aria-label="<?=$this->transEscAttr('select_item_storage_retrieval_request_cancel')?>">
</label>
</div>
<?php endif; ?>
Expand Down Expand Up @@ -81,11 +82,11 @@
if (is_a($resource, 'VuFind\\RecordDriver\\SolrDefault') && !is_a($resource, 'VuFind\\RecordDriver\\Missing')) {
$title = $resource->getTitle();
$title = empty($title) ? $this->transEsc('Title not available') : $this->escapeHtml($title);
echo '<a href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
echo '<a id="' . $describedById . '" href="' . $this->escapeHtmlAttr($this->recordLinker()->getUrl($resource))
. '" class="title">' . $title . '</a>';
} elseif (isset($ilsDetails['title']) && !empty($ilsDetails['title'])) {
// If the record is not available in Solr, perhaps the ILS driver sent us a title we can show...
echo $this->escapeHtml($ilsDetails['title']);
echo '<span id="' . $describedById . '">' . $this->escapeHtml($ilsDetails['title']) . '</span>';
} else {
// Last resort -- indicate that no title could be found.
echo $this->transEsc('Title not available');
Expand Down
4 changes: 2 additions & 2 deletions themes/bootstrap3/templates/record/cart-buttons.phtml
Expand Up @@ -2,11 +2,11 @@
<?php if ($cart->isActive()): ?>
<?php $cartId = $this->source . '|' . $this->id; ?>
<span class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>">
<a href="#" class="cart-add result-link icon-link hidden<?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>">
<a href="#" class="cart-add result-link icon-link hidden<?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>" aria-describedby="<?=$cartId?>">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should $this->escapeHtmlAttr($cartId) to ensure valid HTML.

Also note that this template can be rendered as part of the record view as well as in the search results. In the context of the record view, there will be no element with the ID of $cartId on the page. Maybe something like RecordDriver/DefaultRecord/core.phtml needs to be adjusted to include an ID to avoid inconsistencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RLangeUni do you know about that problem in our environment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the examples I just checked have the ID of the record as $cartId, that must be always available in the record view. Maybe I do misunderstand something here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$cartId is the value used by the Javascript-based cart system to represent the item -- in the format backendId|recordId -- but it is not necessarily an HTML element ID on the page. In fact, because recordId is not constrained to any particular set of characters, it's even possible that this is not a legal HTML element ID. I think we need a different mechanism for associating these things together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RLangeUni do you know how we handle that? Do you have an idea how to solve this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed in finc we are using an extra attribut in cart/contents. I added it in commit c42074dde3.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we could ignore the buttons / leave them out for a later PR and concentrate on the checkboxes only.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for now there is no unique-id on the detail record page and the aria-describedby within the cart button is useless, but it should do no harm? Eventually we must avoid duplicate ids (I think the case should not occur).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for this PR we can just move the aria-describedby="<?=$cartId?>" inside the if-condition <?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>" and make further improvements (like getUniqueIdWithSource()) in another PR?
What do you think @demiankatz @RLangeUni ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that helps -- the issue is that aria-describedby needs to refer to an HTML element ID, but $cartId is an identifier used by the cart's Javascript code. There is no guarantee that a matching HTML element exists anywhere, so in many cases, this is going to be a broken reference that doesn't accomplish anything.

<?=$this->icon('cart-add', 'icon-link__icon') ?>
<span class="icon-link__label"><?=$this->transEsc('Add to Book Bag') ?></span>
</a>
<a href="#" class="cart-remove result-link icon-link hidden<?php if ($cart->contains($cartId)): ?> correct<?php endif ?>">
<a href="#" class="cart-remove result-link icon-link hidden<?php if ($cart->contains($cartId)): ?> correct<?php endif ?>" aria-describedby="<?=$cartId?>">
<?=$this->icon('cart-remove', 'icon-link__icon') ?>
<span class="icon-link__label"><?=$this->transEsc('Remove from Book Bag') ?></span>
</a>
Expand Down
4 changes: 2 additions & 2 deletions themes/bootstrap3/templates/record/checkbox.phtml
@@ -1,6 +1,6 @@
<?php $label = isset($this->context) ? 'select_item_' . $this->context : 'select_item'; ?>
<label class="record-checkbox hidden-print">
<input class="checkbox-select-item" type="checkbox" name="ids[]" value="<?=$this->escapeHtmlAttr($this->id) ?>"<?php if (isset($this->formAttr)): ?> form="<?=$this->formAttr ?>"<?php endif; ?>>
<input class="checkbox-select-item" type="checkbox" name="ids[]" value="<?=$this->escapeHtmlAttr($this->id) ?>"<?php if (isset($this->formAttr)): ?> form="<?=$this->formAttr ?>"<?php endif; ?> aria-describedby="<?=$this->escapeHtmlAttr($this->id) ?>" aria-label="<?=$this->transEscAttr($label)?>">
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
<span class="checkbox-icon"></span>
<?php if (strlen($this->number ?? '') > 0): ?><span class="sr-only"><?=$this->transEsc('result_checkbox_label', ['%%number%%' => $this->number]) ?></span><?php endif; ?>
</label>
<input type="hidden" name="idsAll[]" value="<?=$this->escapeHtmlAttr($this->id) ?>"<?php if (isset($this->formAttr)): ?> form="<?=$this->formAttr ?>"<?php endif; ?>>
3 changes: 1 addition & 2 deletions themes/bootstrap3/templates/search/bulk-action-buttons.phtml
Expand Up @@ -3,8 +3,7 @@
<div class="bulk-checkbox">
<input type="checkbox" class="checkbox-select-all" name="selectAll" id="<?=$this->idPrefix?>addFormCheckboxSelectAll"<?php if ($this->formAttr): ?> form="<?=$this->escapeHtmlAttr($this->formAttr) ?>"<?php endif; ?>>
<label for="<?=$this->idPrefix?>addFormCheckboxSelectAll">
<?=$this->transEsc('select_page')?>
&#124; <?=$this->transEsc('with_selected')?>:
<?=$this->transEsc('select_page')?>:
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
</label>
</div>
<ul class="action-toolbar">
Expand Down