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

Merge accessibility changes into main #2463

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
89713b4
Install `stimulus`
Lubosky Jul 17, 2023
e0f7d95
Start integrating `stimulus` into admin UI
Lubosky Jul 17, 2023
5592da2
Update Webpack config
Lubosky Jul 17, 2023
3925d25
Include `application-admin.js` within admin layouts
Lubosky Jul 17, 2023
e3001d3
Add testing controllers
Lubosky Jul 17, 2023
ca8b55c
Add `Procfile.webpack` to make Webpack initialization optional
Lubosky Jul 17, 2023
d24ea21
Focus form element when it appears
Lubosky Jul 17, 2023
9bf7403
Make `focus` fn accessible via `window`
Lubosky Jul 18, 2023
365100e
Focus form elements when they appear on settings page
Lubosky Jul 18, 2023
d8ed7cb
Make password errors more accessible
TheDancingClown Jul 18, 2023
7e2d610
Use Stimulus controller to add/remove focus
Lubosky Jul 19, 2023
cce7bfe
Update partials to use Stimulus controller
Lubosky Jul 19, 2023
0d41fab
Focusable accordions
Lubosky Jul 19, 2023
2527ddd
Fix focus on bulk assign link
TheDancingClown Jul 19, 2023
bb5bdc5
Change font size on word count
TheDancingClown Jul 19, 2023
314e643
Adds focus indicator to select element
TheDancingClown Jul 20, 2023
a84c263
Add 'Appraisal view of application' to page title when logged in as a…
DaniBitZesty Jul 20, 2023
36ad250
Use hidden field so elements can not be tabbed into
TheDancingClown Jul 20, 2023
6d4f609
JS: Add `form-flash` controller
Lubosky Jul 25, 2023
28e2001
Listen to `ajax` events & pass them further (due to `jquery_ujs`)
Lubosky Jul 25, 2023
2371dee
Helper function for displaying generic flash messages when JS is disa…
Lubosky Jul 25, 2023
608f863
Render generic flash message when comment is modified
Lubosky Jul 25, 2023
0cb295e
Add `data-controller` attribute
Lubosky Jul 25, 2023
5643595
Update JS files to trigger `ajax:success` events manually
Lubosky Jul 25, 2023
1ea19be
JS: Rename & rework inline flash controller
Lubosky Jul 27, 2023
444e579
Use controller w/ HTML element targets to display flash messages
Lubosky Jul 27, 2023
0b855cd
Add non-JS flash messages
Lubosky Jul 27, 2023
d412689
Merge pull request #2425 from bitzesty/focus-management-appear-disappear
Lubosky Jul 27, 2023
dbc7cb9
Merge pull request #2439 from bitzesty/remote-submit-flash-messages
Lubosky Jul 27, 2023
112c317
Adds custom front end validation errors for innovation
TheDancingClown Jul 26, 2023
494055a
Adds custom backend validation errors for innovation
TheDancingClown Jul 27, 2023
e52f73b
Adds custom front end validation errors for sd
TheDancingClown Jul 28, 2023
4b1ffdc
Adds custom front end validation errors for po
TheDancingClown Jul 28, 2023
ffe7bdf
Adds custom front end validation errors for IT
TheDancingClown Jul 28, 2023
4a1c227
Adds hints for subfield fields
TheDancingClown Jul 28, 2023
ec3347a
non js form step-by-step validation
dreamfall Jul 31, 2023
a93cbd0
Merge pull request #2443 from bitzesty/error-messages
TheDancingClown Jul 31, 2023
c0dbf95
Merge pull request #2430 from bitzesty/dropdown-focus
TheDancingClown Jul 31, 2023
4891590
Merge pull request #2428 from bitzesty/word-count
TheDancingClown Jul 31, 2023
1a0854f
use hide class instead of visuallyhidden
TheDancingClown Jul 31, 2023
3c1011c
Merge pull request #2435 from bitzesty/form-labels
TheDancingClown Jul 31, 2023
2a9c335
Merge branch 'a11y-2023' into focus
TheDancingClown Jul 31, 2023
9a46566
Merge pull request #2424 from bitzesty/password-a11y
TheDancingClown Jul 31, 2023
e1363f4
Merge pull request #2427 from bitzesty/focus
TheDancingClown Jul 31, 2023
50ec231
Revert "Adds accessible error messages"
TheDancingClown Jul 31, 2023
f334638
Merge pull request #2446 from bitzesty/revert-2443-error-messages
TheDancingClown Jul 31, 2023
3af3d6e
Merge remote-tracking branch 'origin/error-messages' into fix-js-form…
dreamfall Aug 1, 2023
cb829f4
Revert "Revert "Adds accessible error messages""
TheDancingClown Aug 3, 2023
e49692d
Merge pull request #2459 from bitzesty/revert-2446-revert-2443-error-…
TheDancingClown Aug 3, 2023
e46486e
Merge pull request #2462 from bitzesty/fix-js-form-errors
TheDancingClown Aug 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Procfile.webpack
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
web: bundle exec rake cf:run_migrations db:migrate && bin/rails server -p $PORT
worker: bundle exec sidekiq -L ./log/worker.log -C ./config/sidekiq.yml
webpack: bin/webpack-dev-server
5 changes: 3 additions & 2 deletions app/assets/javascripts/admin/comments.js.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ready = ->
$('body').on 'submit', '.new_comment', (e) ->
$(document).on 'submit', '.new_comment', (e) ->
that = $(this)
e.preventDefault()
$.ajax
Expand Down Expand Up @@ -30,8 +30,9 @@ ready = ->
else
signatureWrapper.html("")

window.fire(that[0], 'ajax:x:success', data)

$('body').on 'submit', '.destroy-comment', (e) ->
$(document).on 'submit', '.destroy-comment', (e) ->
e.preventDefault()
$.ajax
url: $(this).attr('action')
Expand Down
13 changes: 6 additions & 7 deletions app/assets/javascripts/admin/financial_data.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ jQuery ->
overallBenchmarksTable = ($ "#overall-financial-benchmarks")
financialTable = ($ "#financial-table")

($ "input", form).on "change keyup keydown paste", ->
timer ||= setTimeout(saveFinancials, 500 )
$("input", form).on "change keyup keydown paste", ->
timer ||= setTimeout(saveFinancials, 500)

($ "button", form).on "click", (event) ->
do event.preventDefault
$(this).closest(".form-group").removeClass("form-edit")


updateExportsGrowth = (exports) ->
exportsGrowth = ($ 'tr.exports-growth td.value', benchmarksTable)
values = exports.map (i, td) ->
Expand Down Expand Up @@ -91,13 +90,13 @@ jQuery ->
url = form.attr('action')
formData = form.serialize()
updateBenchmarks()

jqXHR = $.ajax({
$.ajax
url: url
data: formData
type: 'POST'
dataType: 'js'
})
dataType: 'script'
success: (_data) ->
window.fire(form[0], 'ajax:x:success', null)

($ 'td.value', financialTable).each (i, td) ->
input = ($ 'input', ($ td))
Expand Down
25 changes: 25 additions & 0 deletions app/assets/javascripts/admin/focusable.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FOCUSABLE_ELEMENTS = 'input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [href], [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)'
FOCUSABLE_FORM_ELEMENTS = 'input:not([disabled]), select:not([disabled]), textarea:not([disabled])'

window.FOCUSABLE_ELEMENTS = FOCUSABLE_ELEMENTS
window.FOCUSABLE_FORM_ELEMENTS = FOCUSABLE_FORM_ELEMENTS

visible = (el) ->
!el.hidden and (!el.type or el.type != 'hidden') and (el.offsetWidth > 0 or el.offsetHeight > 0)

focusable = (el) ->
el.tabIndex >= 0 and !el.disabled and visible(el)

window.focusElement = (el, elements = FOCUSABLE_ELEMENTS) ->
autofocusElement = Array.from(el.querySelectorAll(elements)).filter(focusable)[0]
if autofocusElement
autofocusElement.focus()
return

window.autofocusElement = (el) ->
autofocusElement = Array.from(el.querySelectorAll('[autofocus]')).filter(focusable)[0]
if !autofocusElement
autofocusElement = el
el.setAttribute('tabindex', '-1')
autofocusElement.focus()
return
19 changes: 13 additions & 6 deletions app/assets/javascripts/admin/form_answers.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,12 @@ ready = ->
if $('.form_answer_attachment').length == 0
sidebarSection.find(".document-list .p-empty").removeClass("visuallyhidden")

$(document).on "click", ".form-edit-link", (e) ->
$(document).on 'click', '.form-edit-link', (e) ->
e.preventDefault()
$(this).closest(".form-group").addClass("form-edit")
element = this.closest('.form-group')
if (element)
element.classList.add('form-edit')

$(".submit-assessment").on "ajax:error", (e, data, status, xhr) ->
errors = data.responseJSON
$(this).addClass("field-with-errors")
Expand Down Expand Up @@ -431,24 +434,28 @@ editFormAnswerAutoUpdate = ->
$(".sic-code .form-save-link").on "click", (e) ->
e.preventDefault()
e.stopPropagation()
that = $("#form_answer_sic_code")
form = $(".edit_form_answer")
input = $("#form_answer_sic_code")
form = $(e.target).closest('form')
$.ajax
action: form.attr("action")
data: form.serialize()
method: "PATCH"
dataType: "json"

success: (result) ->
formGroup = that.parents(".form-group")
formGroup = input.parents(".form-group")
formGroup.removeClass("form-edit")
formGroup.find(".form-value p").text(that.find("option:selected").text())
console.log(formGroup, input)
formGroup.find(".form-value p").text(input.val())
sicCodes = result["form_answer"]["sic_codes"]
counter = 1
for row in $(".sector-average-growth td")
$(row).text(sicCodes[counter.toString()])
counter += 1
$(".avg-growth-legend").text(result["form_answer"]["legend"])

window.fire(form[0], 'ajax:x:success', null)

bindRags =(klass) ->
$(document).on "click", "#{klass} .btn-rag .dropdown-menu a", (e) ->
e.preventDefault()
Expand Down
8 changes: 4 additions & 4 deletions app/assets/javascripts/admin/settings.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,20 @@ jQuery ->
settingsWrapper.on "click", ".btn-cancel", (e) ->
e.preventDefault()

form_well = ($ e.currentTarget).closest('.well')
well = ($ e.currentTarget).closest('.well')

if form_well.hasClass("deadline-form")
if well.hasClass("deadline-form")
wrapper = ($ e.currentTarget).closest('.deadline')
($ ".form-value", wrapper).removeClass("hidden")
($ ".deadline-form", wrapper).addClass("hidden")
($ ".edit-deadline", wrapper).removeClass("hidden")

else if form_well.hasClass("notification-edit-form")
else if well.hasClass("notification-edit-form")
wrapper = ($ e.currentTarget).closest('li')
($ ".form-value", wrapper).removeClass("hidden")
($ ".notification-edit-form", wrapper).addClass("hidden")
($ ".actions", wrapper).removeClass("hidden")

else if form_well.hasClass("notification-new-form")
else if well.hasClass("notification-new-form")
wrapper = ($ e.currentTarget).closest('.panel-section')
($ ".notification-form", wrapper).addClass("hidden")
17 changes: 14 additions & 3 deletions app/assets/javascripts/application-admin.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@
#= require clean-paste

$(document).ready(() ->
$("html").removeClass("no-js").addClass("js")
($ ".timepicker").timePicker()
($ ".datepicker").datepicker({dateFormat: "dd/mm/yy"})
$('html').removeClass('no-js').addClass('js')
($ '.timepicker').timePicker()
($ '.datepicker').datepicker({dateFormat: 'dd/mm/yy'})
)

$(document).on 'ajax:success', 'form', (event, data, _status, _xhr) ->
fire(this, 'ajax:x:success', data)

$(document).on 'ajax:error', 'form', (event, data, _status, _xhr) ->
fire(this, 'ajax:x:error', data)

window.fire = (obj, name, data) ->
event = new CustomEvent(name, detail: data, bubbles: true, cancelable: true)
obj.dispatchEvent(event)
!event.defaultPrevented
97 changes: 77 additions & 20 deletions app/assets/javascripts/frontend/form-validation.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,49 @@ window.FormValidation =
for subquestion in subquestions
if not @validateSingleQuestion($(subquestion))
@logThis(question, "validateRequiredQuestion", "This field is required")
@addErrorMessage($(subquestion), "This field is required")
@addSubfieldError(question, subquestion)
else
if not @validateSingleQuestion(question)
@logThis(question, "validateRequiredQuestion", "This field is required")
@addErrorMessage(question, "This field is required")
@addQuestionError(question)

addSubfieldError: (question, subquestion) ->
questionRef = question.attr("data-question_ref")
input = $(subquestion).find('input,textarea,select').filter(':visible')
label = $("label[for='#{input.attr('id')}']").text()
incompleteMessage = "Question #{questionRef} is incomplete. It is required and must be filled in."

if question.hasClass('date-DDMMYYYY')
@addErrorMessage($(subquestion), "#{incompleteMessage} Use the format DD/MM/YYYY.")
else if question.hasClass('date-MMYYYY')
@addErrorMessage($(subquestion), "#{incompleteMessage} Use the format MM/YYYY.")
else if question.hasClass('date-YYYY')
@addErrorMessage($(subquestion), "#{incompleteMessage} Use the format YYYY.")
else if input.hasClass("autocomplete__input")
@addErrorMessage($(subquestion), "Question #{questionRef} is incomplete. #{label} is required and an option must be selected from the following dropdown list.")
else
if question.find(".js-financial-year-latest").length
#avoid duplicate errors for financial year questions
return
else
@addErrorMessage($(subquestion), "Question #{questionRef} is incomplete. #{label} is required and must be filled in.")

addQuestionError: (question) ->
questionRef = question.attr("data-question_ref")
incompleteMessage = "Question #{questionRef} is incomplete. It is required"
if @isOptionsQuestion(question)
@addErrorMessage(question, "#{incompleteMessage} and an option must be chosen from the following list.")
else if @isSelectQuestion(question)
@addErrorMessage(question, "#{incompleteMessage} and an option must be selected from the following dropdown list.")
else if @isTextishQuestion(question) && !question.hasClass("question-year")
@addErrorMessage(question, "#{incompleteMessage} and must be filled in.")
else if question.hasClass("question-year")
@addErrorMessage(question, "#{incompleteMessage} and must be filled in. Use the format YYYY.")
else if @isCheckboxQuestion(question)
if question.find("input[type='checkbox']").length > 1
@addErrorMessage(question, "#{incompleteMessage} and at least one option must be chosen from the following list.")
else
@addErrorMessage(question, "#{incompleteMessage} and confirmation must be given by ticking the checkbox.")

validateMatchQuestion: (question) ->
q = question.find(".match")
Expand All @@ -161,6 +199,7 @@ window.FormValidation =

validateMaxDate: (question) ->
val = question.find("input[type='number']").val()
questionRef = question.attr("data-question_ref")

questionYear = question.find(".js-date-input-year").val()
questionMonth = question.find(".js-date-input-month").val()
Expand All @@ -175,7 +214,7 @@ window.FormValidation =

if not @toDate(questionDate).isValid()
@logThis(question, "validateMaxDate", "Not a valid date")
@addErrorMessage(question, "Not a valid date")
@addErrorMessage(question, "Question #{questionRef} is incomplete. The date entered is not valid. Use the format DD/MM/YYYY.")
return

if diff > 0
Expand Down Expand Up @@ -291,6 +330,7 @@ window.FormValidation =
@addErrorMessage(question, "Not a valid number")

validateEmployeeMin: (question) ->
questionRef = question.attr("data-question_ref")
for subquestion in question.find("input")
shownQuestion = true
for conditional in $(subquestion).parents('.js-conditional-question')
Expand All @@ -299,9 +339,10 @@ window.FormValidation =

if shownQuestion
subq = $(subquestion)
label = $("label[for='#{subq.attr('id')}']").text()
if not subq.val() and question.hasClass("question-required")
@logThis(question, "validateEmployeeMin", "This field is required")
@appendMessage(subq.closest(".span-financial"), "This field is required")
@appendMessage(subq.closest(".span-financial"), "Question #{questionRef} is incomplete. #{label} is required and must be filled in. Enter '0' if none.")
@addErrorClass(question)
continue
else if not subq.val()
Expand Down Expand Up @@ -374,6 +415,7 @@ window.FormValidation =

validateCurrentAwards: (question) ->
$(".govuk-error-message", question).empty()
questionRef = question.attr("data-question_ref")

for subquestion in question.find(".list-add li")
errors = false
Expand All @@ -382,7 +424,7 @@ window.FormValidation =
if !$(input).val()
fieldName = $(input).data("dependable-option-siffix")
fieldName = fieldName[0].toUpperCase() + fieldName.slice(1)
fieldError = "#{fieldName} can't be blank. "
fieldError = "Question #{questionRef} is incomplete. #{fieldName} is required and an option must be selected from the following list. "
@logThis(question, "validateCurrentAwards", fieldError)
@appendMessage($(input).closest('.govuk-form-group'), fieldError)
errors = true
Expand All @@ -402,6 +444,7 @@ window.FormValidation =
subq = $(subquestion)
qParent = subq.closest(".js-fy-entries")
errContainer = subq.closest(".span-financial")
questionRef = question.attr("data-question_ref")

shownQuestion = true
for conditional in $(subquestion).parents('.js-conditional-question')
Expand All @@ -413,7 +456,8 @@ window.FormValidation =

if not subq.val() and question.hasClass("question-required")
@logThis(question, "validateMoneyByYears", "This field is required")
@appendMessage(errContainer, "This field is required")
label = $("label[for='#{subq.attr('id')}']").text()
@appendMessage(errContainer, "Question #{questionRef} is incomplete. #{label} is required and must be filled in. Enter '0' if none.")
@addErrorClass(question)
continue
else if not subq.val()
Expand Down Expand Up @@ -448,41 +492,54 @@ window.FormValidation =
if !conditional
return
# end of conditional validation
questionRef = question.attr("data-question_ref")

for subquestionBlock in question.find(".by-years-wrapper.show-question .govuk-date-input")
subq = $(subquestionBlock)
qParent = subq.closest(".js-fy-entries")
label = qParent.find(".js-year-default").text()
errorsContainer = qParent.find(".govuk-error-message").html()

day = subq.find("input.js-fy-day").val()
month = subq.find("input.js-fy-month").val()
year = subq.find("input.js-fy-year").val()

if (not day or not month or not year)
if question.hasClass("question-required") && errorsContainer.length < 1
if question.hasClass("question-required") && errorsContainer.length < 1
if (not day and not month and not year)
@logThis(question, "validateDateByYears", "This field is required")
@appendMessage(qParent, "This field is required")
@appendMessage(qParent, "Question #{questionRef} is incomplete. #{label} is required and must be filled in. Use the format DD/MM/YYYY.")
@addErrorClass(question)
else
complexDateString = day + "/" + month + "/" + year
date = @toDate(complexDateString)
else if (not day)
@appendMessage(qParent, "Question #{questionRef} is incomplete. #{label} day is required and must be filled in. Use the format DD.")
else if (not month)
@appendMessage(qParent, "Question #{questionRef} is incomplete. #{label} month is required and must be filled in. Use the format MM.")
else if (not year)
@appendMessage(qParent, "Question #{questionRef} is incomplete. #{label} year is required and must be filled in. Use the format YYYY.")
else
complexDateString = day + "/" + month + "/" + year
date = @toDate(complexDateString)

if not date.isValid()
@logThis(question, "validateDateByYears", "Not a valid date")
@appendMessage(qParent, "Not a valid date")
@addErrorClass(question)
if not date.isValid()
@logThis(question, "validateDateByYears", "Not a valid date")
@appendMessage(qParent, "Not a valid date")
@addErrorClass(question)

validateInnovationFinancialDate: (question) ->

val = question.find("input[type='number']").val()

questionRef = question.attr("data-question_ref")
questionDay = parseInt(question.find(".innovation-day").val())
questionMonth = parseInt(question.find(".innovation-month").val())
questionDate = "#{questionDay}/#{questionMonth}/#{moment().format('Y')}"

if not @toDate(questionDate).isValid()
if (not questionDay and not questionMonth)
@addErrorMessage(question, "Question #{questionRef} is incomplete. Year-end is required and must be filled in. Use the format DD/MM.")
else if not questionDay
@addErrorMessage(question, "Question #{questionRef} is incomplete. Day is required and must be filled in. Use the format DD/MM.")
else if not questionMonth
@addErrorMessage(question, "Question #{questionRef} is incomplete. Month is required and must be filled in. Use the format DD/MM.")
else if not @toDate(questionDate).isValid()
@logThis(question, "validateMaxDate", "Not a valid date")
@addErrorMessage(question, "Not a valid date")
@addErrorMessage(question, "Question #{questionRef} is incomplete. It is required and must be filled in. Use the format DD/MM.")
return

validateDiffBetweenDates: (question) ->
Expand Down
3 changes: 3 additions & 0 deletions app/assets/javascripts/frontend/js_detector.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
jQuery ->
# if the user has javascript enabled, remove non js step value
$("#non_js_step_title").val("")
Loading
Loading