Skip to content

Commit

Permalink
Add aria-rowindex attributes to table rows for improved screen reader…
Browse files Browse the repository at this point in the history
… accessibility
  • Loading branch information
mirotriad committed Jan 30, 2025
1 parent 6eaf90c commit f3d6392
Show file tree
Hide file tree
Showing 17 changed files with 336 additions and 95 deletions.
10 changes: 10 additions & 0 deletions app/helpers/investigations_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,16 @@ def non_search_cases_page_names
%w[team_cases your_cases assigned_cases].freeze
end

ROWS_PER_INVESTIGATION = 4

# Ensures sequential ARIA row indices across paginated table sections.
# For example: With 4 rows per investigation, the second investigation's
# rows will start at index 5 (1-based), continuing the sequence from
# the first investigation's rows 1-4.
def calculate_row_index(counter, row_number, rows_per_item = ROWS_PER_INVESTIGATION)
(counter * rows_per_item) + row_number
end

private

def search_result_values(_search_terms, number_of_results)
Expand Down
40 changes: 22 additions & 18 deletions app/views/investigations/_full_table_body.html.erb
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
<tbody class="govuk-table__body" data-cy-case-id="<%= investigation.pretty_id %>">
<tr class="govuk-table__row">
<th class="govuk-visually-hidden">
Notification title
</th>
<th id="<%= dom_id investigation, :item %>" colspan="5" scope="colgroup" class="govuk-table__header">
<%= link_to sanitize(investigation.title), investigation_path(investigation), class: "govuk-link govuk-link--no-visited-state" %>
<tr class="govuk-table__row" id="<%= dom_id investigation, :title %>" aria-rowindex="<%= calculate_row_index(investigation_counter, 1) %>">
<th scope="row" class="govuk-table__header" colspan="5" headers="case">
<span id="desc-<%= investigation.id %>" class="govuk-visually-hidden">
Notification number: <%= investigation.pretty_id %>.
<%= non_search_cases_page_names.include?(@page_name) ? "Created: #{investigation.created_at.to_formatted_s(:govuk)}" : "Owner: #{investigation_owner(investigation)}" %>.
<%= @page_name == "assigned_cases" ? "Assigner: #{sanitize(investigation.owner_team&.name)}" : "Hazard type: #{sanitize(investigation.hazard_type.presence || "Not provided")}" %>.
Product count: <%= pluralize investigation.products.count, "product" %>.
<%= sorted_by == SortByHelper::SORT_BY_CREATED_AT ? "Created: #{time_ago_in_words(investigation.created_at)} ago" : "Updated: #{date_or_recent_time_ago investigation.updated_at}" %>.
</span>
<%= link_to sanitize(investigation.title), investigation_path(investigation), class: "govuk-link govuk-link--no-visited-state", "aria-describedby": "desc-#{investigation.id}" %>
</th>
</tr>
<tr class="govuk-table__row">
<th headers="<%= dom_id investigation, :item %>" id="<%= dom_id investigation, :meta %>" class="govuk-visually-hidden">
<tr class="govuk-table__row" id="<%= dom_id investigation, :meta %>" aria-rowindex="<%= calculate_row_index(investigation_counter, 2) %>">
<th headers="case" id="<%= dom_id investigation, :meta_header %>" class="govuk-visually-hidden">
Metadata
</th>
<td headers="case <%= dom_id investigation, :item %> <%= dom_id investigation, :meta %>" class="govuk-table__cell">
<td headers="case <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= investigation.pretty_id %>
</td>

<% if non_search_cases_page_names.include? @page_name %>
<td headers="casecreated <%= dom_id investigation, :item %> <%= dom_id investigation, :meta %>" class="govuk-table__cell">
<td headers="casecreated <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= investigation.created_at.to_formatted_s(:govuk) %>
</td>
<% else %>
<td headers="caseowner <%= dom_id investigation, :item %> <%= dom_id investigation, :meta %>" class="govuk-table__cell">
<%= sanitize(investigation.owner_display_name_for(viewer: current_user)) %>
<td headers="caseowner <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= investigation_owner(investigation) %>
</td>
<% end %>

<% if @page_name == "assigned_cases" %>
<td headers="assigner" class="govuk-table__cell">
<td headers="assigner <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= sanitize(investigation.owner_team&.name) %>
</td>
<% else %>
<td headers="haztype" class="govuk-table__cell">
<td headers="haztype <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= sanitize(investigation.hazard_type.presence || "Not provided") %>
</td>
<% end %>

<td headers="product_count" class="govuk-table__cell">
<td headers="prodcount <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= pluralize investigation.products.count, "product" %>
</td>

<% if sorted_by == SortByHelper::SORT_BY_CREATED_AT %>
<td headers="created <%= dom_id investigation, :item %> <%= dom_id investigation, :meta %>" class="govuk-table__cell">
<td headers="created <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= "#{time_ago_in_words(investigation.created_at)} ago" %>
</td>
<% else %>
<td headers="updated <%= dom_id investigation, :item %> <%= dom_id investigation, :meta %>" class="govuk-table__cell">
<td headers="updated <%= dom_id investigation, :meta_header %>" class="govuk-table__cell">
<%= date_or_recent_time_ago investigation.updated_at %>
</td>
<% end %>
</tr>

<%= render "investigations/status_table_row", investigation: investigation %>
<%= render "investigations/status_table_row", investigation: investigation, investigation_counter: investigation_counter %>
</tbody>
8 changes: 4 additions & 4 deletions app/views/investigations/_highlight_table_body.html.erb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<% displayable_highlights = get_displayable_highlights(highlights, investigation) %>

<tbody class="govuk-table__body">
<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 1) %>">
<th class="govuk-visually-hidden">
Notification title
</th>
<th id="<%= dom_id investigation, :item %>" colspan="4" scope="colgroup" class="govuk-table__header">
<%= link_to sanitize(investigation.title), investigation_path(investigation), class: "govuk-link govuk-link--no-visited-state" %>
</th>
</tr>
<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 2) %>">
<th headers="<%= dom_id investigation, :item %>" id="<%= dom_id investigation, :meta %>" class="govuk-visually-hidden">
Metadata
</th>
Expand All @@ -29,9 +29,9 @@
</td>
<% end %>
</tr>
<%= render "investigations/status_table_row", investigation: investigation %>
<%= render "investigations/status_table_row", investigation: investigation, investigation_counter: investigation_counter %>
<% if displayable_highlights.any? %>
<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 3) %>">
<th headers="<%= dom_id investigation, :item %>" id="<%= dom_id investigation, :keywords %>" class="govuk-visually-hidden">
Matching keywords
</th>
Expand Down
6 changes: 3 additions & 3 deletions app/views/investigations/_restricted_table_body.html.erb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<tbody class="govuk-table__body">
<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 1) %>">
<th class="govuk-visually-hidden">
Notification title
</th>
<th id="<%= dom_id investigation, :item %>" colspan="4" scope="colgroup" class="govuk-table__header">
<%= "#{sanitize(investigation.case_type&.capitalize)} restricted" %>
</th>
</tr>
<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 2) %>">
<th headers="<%= dom_id investigation, :item %>" id="<%= dom_id investigation, :meta %>" class="govuk-visually-hidden">
Metadata
</th>
Expand All @@ -18,5 +18,5 @@
&nbsp;
</td>
</tr>
<%= render "investigations/status_table_row", investigation: investigation %>
<%= render "investigations/status_table_row", investigation: investigation, investigation_counter: investigation_counter %>
</tbody>
19 changes: 18 additions & 1 deletion app/views/investigations/_status_table_row.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<% end %>
<% end %>

<tr class="govuk-table__row">
<tr class="govuk-table__row" aria-rowindex="<%= calculate_row_index(investigation_counter, 3) %>">
<th headers="<%= dom_id investigation, :item %>" id="<%= dom_id investigation, :status %>" class="govuk-visually-hidden">
Type
</th>
Expand All @@ -37,3 +37,20 @@
<% end %>
</td>
</tr>

<tr class="govuk-table__row" id="<%= dom_id investigation, :status %>" aria-rowindex="<%= calculate_row_index(investigation_counter, 4) %>">
<th headers="<%= dom_id investigation, :title %>" id="<%= dom_id investigation, :status_header %>" class="govuk-visually-hidden">
Status
</th>
<td headers="case <%= dom_id investigation, :status_header %>" class="govuk-table__cell" colspan="5">
<% if investigation.is_a?(Investigation::Notification) && investigation.draft? %>
<%= link_to "Resume draft", resume_draft_notification_path(investigation), class: "govuk-link govuk-link--no-visited-state" %>
<% else %>
<% if investigation.is_closed? %>
<%= link_to "View closed case", investigation_path(investigation), class: "govuk-link govuk-link--no-visited-state" %>
<% else %>
<%= link_to "View case", investigation_path(investigation), class: "govuk-link govuk-link--no-visited-state" %>
<% end %>
<% end %>
</td>
</tr>
13 changes: 13 additions & 0 deletions app/views/investigations/_status_tag.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<%
status_class = case investigation.status
when "open"
"govuk-tag--blue"
when "closed"
"govuk-tag--red"
else
"govuk-tag--grey"
end
%>
<strong class="govuk-tag <%= status_class %>" aria-label="Status: <%= investigation.status %>">
<%= investigation.status.titleize %>
</strong>
4 changes: 2 additions & 2 deletions app/views/investigations/_table_body.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<% if policy(investigation).view_non_protected_details? %>
<%= render "investigations/full_table_body", investigation: investigation.decorate, sorted_by: sorted_by, page_name: page_name %>
<%= render "investigations/full_table_body", investigation: investigation.decorate, sorted_by: sorted_by, page_name: page_name, investigation_counter: investigation_counter %>
<% else %>
<%= render "investigations/restricted_table_body", investigation: investigation.decorate, page_name: page_name %>
<%= render "investigations/restricted_table_body", investigation: investigation.decorate, page_name: page_name, investigation_counter: investigation_counter %>
<% end %>
5 changes: 2 additions & 3 deletions app/views/investigations/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<% if @investigations.any? %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-full" role="region" aria-label="Cases">
<table class="govuk-table opss-table-items opss-table-items--sm">
<table class="govuk-table opss-table-items opss-table-items--sm" role="table">
<caption class="govuk-visually-hidden">
Notifications data: 5 columns with each notification described across rows within each table body.
</caption>
Expand All @@ -42,8 +42,7 @@
<% else %>
<th id="haztype" scope="col" class="govuk-table__header">Hazard type</th>
<% end %>

<th scope="col" class="govuk-table__header">Product count</th>
<th id="prodcount" scope="col" class="govuk-table__header">Product count</th>

<% if query_params[:sort_by] == SortByHelper::SORT_BY_CREATED_AT && non_search_cases_page_names.exclude?(@page_name) %>
<th id="created" scope="col" class="govuk-table__header">Created</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@
<% if @records.any? %>
<p class="govuk-body"><% if @records_count == 1 %>There is currently 1 business.<% else %>There are currently <%= @records_count %> businesses.<% end %></p>
<%=
govuk_table do |table|
govuk_table(html_attributes: { role: "table" }) do |table|
table.with_head do |head|
head.with_row do |row|
row.with_cell(text: "Trading name")
row.with_cell(text: "Registered or Legal name")
row.with_cell(text: "Companies House number")
row.with_cell(text: "Address")
row.with_cell(text: "<span class=\"govuk-visually-hidden\">Select business</span>".html_safe)
row.with_cell(text: "Trading name", header: true, html_attributes: { scope: "col" })
row.with_cell(text: "Registered or Legal name", header: true, html_attributes: { scope: "col" })
row.with_cell(text: "Companies House number", header: true, html_attributes: { scope: "col" })
row.with_cell(text: "Address", header: true, html_attributes: { scope: "col" })
row.with_cell(text: "<span class=\"govuk-visually-hidden\">Select business</span>".html_safe, header: true, html_attributes: { scope: "col" })
end
end

Expand All @@ -86,20 +86,26 @@
end.join("<hr class=\"govuk-section-break govuk-section-break--m govuk-section-break--visible\">")
end

select_button = form_with url: "#{wizard_path}?business_id=#{record.id}", method: :put, builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f|
f.govuk_submit "Select", name: "draft", value: true, secondary: true
select_button = if @existing_business_ids.include?(record.id)
""
else
"<span id=\"desc-#{record.id}\" class=\"govuk-visually-hidden\">
Trading name: #{sanitize(record.trading_name)}.
Registered or Legal name: #{sanitize(record.legal_name)}.
Companies House number: #{sanitize(record.company_number)}.
Address: #{sanitize(addresses)}.
</span>".html_safe +
form_with(url: "#{wizard_path}?business_id=#{record.id}", method: :put, builder: GOVUKDesignSystemFormBuilder::FormBuilder) do |f|
f.govuk_submit "Select", name: "draft", value: true, secondary: true, "aria-describedby": "desc-#{record.id}"
end
end

body.with_row do |row|
row.with_cell(text: sanitize(record.trading_name))
row.with_cell(text: sanitize(record.legal_name))
row.with_cell(text: sanitize(record.company_number))
row.with_cell(text: sanitize(addresses).html_safe)
if @existing_business_ids.include?(record.id)
row.with_cell(text: "")
else
row.with_cell(text: select_button)
end
row.with_cell(text: select_button.html_safe)
end
end
end
Expand Down
32 changes: 17 additions & 15 deletions app/views/notifications/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th class="govuk-visually-hidden">&nbsp;</th>
<th id="case" scope="col" class="govuk-table__header">Notification number</th>
<th id="case" scope="col" class="govuk-table__header" aria-label="Notification number">Notification number</th>
<% if non_search_cases_page_names.include? @page_name %>
<th id="casecreated" scope="col" class="govuk-table__header">Notification created</th>
<th id="casecreated" scope="col" class="govuk-table__header" aria-label="Notification created">Notification created</th>
<% else %>
<th id="caseowner" scope="col" class="govuk-table__header">Notification owner</th>
<th id="caseowner" scope="col" class="govuk-table__header" aria-label="Notification owner">Notification owner</th>
<% end %>

<% if @page_name == "assigned_cases" %>
<th id="assigner" scope="col" class="govuk-table__header">Assigner</th>
<th id="assigner" scope="col" class="govuk-table__header" aria-label="Assigner">Assigner</th>
<% else %>
<th id="haztype" scope="col" class="govuk-table__header">Hazard type</th>
<th id="haztype" scope="col" class="govuk-table__header" aria-label="Hazard type">Hazard type</th>
<% end %>

<th scope="col" class="govuk-table__header">Product count</th>
<th id="prodcount" scope="col" class="govuk-table__header" aria-label="Product count">Product count</th>

<% if query_params[:sort_by] == SortByHelper::SORT_BY_CREATED_AT && non_search_cases_page_names.exclude?(@page_name) %>
<th id="created" scope="col" class="govuk-table__header">Created</th>
<th id="created" scope="col" class="govuk-table__header" aria-label="Created">Created</th>
<% else %>
<th id="updated" scope="col" class="govuk-table__header">Updated</th>
<th id="updated" scope="col" class="govuk-table__header" aria-label="Updated">Updated</th>
<% end %>
</tr>
</thead>
Expand All @@ -63,21 +63,23 @@
<tfoot class="govuk-table__head">
<tr class="govuk-table__row">
<th class="govuk-visually-hidden">&nbsp;</th>
<th scope="col" class="govuk-table__header">Notification number</th>
<th scope="col" class="govuk-table__header">Notification <%= non_search_cases_page_names.include?(@page_name) ? "created" : "owner" %></th>
<th scope="col" class="govuk-table__header" aria-label="Notification number">Notification number</th>
<th scope="col" class="govuk-table__header" aria-label="<%= non_search_cases_page_names.include?(@page_name) ? "Notification created" : "Notification owner" %>">
Notification <%= non_search_cases_page_names.include?(@page_name) ? "created" : "owner" %>
</th>

<% if @page_name == "assigned_cases" %>
<th scope="col" class="govuk-table__header">Assigner</th>
<th scope="col" class="govuk-table__header" aria-label="Assigner">Assigner</th>
<% else %>
<th scope="col" class="govuk-table__header">Hazard type</th>
<th scope="col" class="govuk-table__header" aria-label="Hazard type">Hazard type</th>
<% end %>

<th scope="col" class="govuk-table__header">Product count</th>
<th scope="col" class="govuk-table__header" aria-label="Product count">Product count</th>

<% if query_params[:sort_by] == SortByHelper::SORT_BY_CREATED_AT && non_search_cases_page_names.exclude?(@page_name) %>
<th scope="col" class="govuk-table__header">Created</th>
<th scope="col" class="govuk-table__header" aria-label="Created">Created</th>
<% else %>
<th scope="col" class="govuk-table__header">Updated</th>
<th scope="col" class="govuk-table__header" aria-label="Updated">Updated</th>
<% end %>
</tr>
</tfoot>
Expand Down
Loading

0 comments on commit f3d6392

Please sign in to comment.