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

Prevent inputs ending up off screen or obscured by keyboards when linking from the error summary to inputs within a large fieldset #1570

Merged
merged 5 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes
- [Pull request #1574: Make form elements scale correctly when text resized by user](https://github.com/alphagov/govuk-frontend/pull/1574).
- [Pull request ##1570: Prevent inputs ending up off screen or obscured by keyboards when linking from the error summary to inputs within a large fieldset](https://github.com/alphagov/govuk-frontend/pull/1570)

## 3.2.0 (Feature release)

Expand Down
91 changes: 91 additions & 0 deletions app/views/examples/error-summary/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{% from "button/macro.njk" import govukButton %}
{% from "date-input/macro.njk" import govukDateInput %}
{% from "error-summary/macro.njk" import govukErrorSummary %}
{% from "fieldset/macro.njk" import govukFieldset %}
{% from "file-upload/macro.njk" import govukFileUpload %}
{% from "input/macro.njk" import govukInput %}
{% from "select/macro.njk" import govukSelect %}
Expand Down Expand Up @@ -63,6 +64,14 @@
{
"text": "Problem with conditionally-revealed input",
"href": "#yes-input"
},
{
"text": "Problem with radios with big heading",
"href": "#radios-big-heading"
},
{
"text": "Problem with input within large fieldset",
"href": "#address-postcode"
}
]
}) }}
Expand Down Expand Up @@ -308,6 +317,88 @@
]
}) }}

{{ govukRadios({
classes: "govuk-radios--inline",
idPrefix: "radios-big-heading",
name: "radios-big-heading",
fieldset: {
legend: {
text: "Have you supplied orders signed in triplicate, sent in, sent back, queried, lost, found, subjected to public inquiry, lost again, and finally buried in soft peat for three months and recycled as firelighters?",
Copy link
Contributor

Choose a reason for hiding this comment

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

isPageHeading: true,
classes: "govuk-fieldset__legend--xl test-radios-big-heading-legend"
}
},
errorMessge: {
text: "Select whether you have supplied orders meeting these criteria"
},
items: [
{
value: "yes",
text: "Yes"
},
{
value: "no",
text: "No"
}
]
}) }}

{% call govukFieldset({
legend: {
text: "What is your address?",
classes: "govuk-fieldset__legend--xl",
isPageHeading: true
}
}) %}

{{ govukInput({
label: {
html: 'Building and street <span class="govuk-visually-hidden">line 1 of 2</span>'
},
id: "address-line-1",
name: "address-line-1"
}) }}

{{ govukInput({
label: {
html: '<span class="govuk-visually-hidden">Building and street line 2 of 2</span>'
},
id: "address-line-2",
name: "address-line-2"
}) }}

{{ govukInput({
label: {
text: "Town or city"
},
classes: "govuk-!-width-two-thirds",
id: "address-town",
name: "address-town"
}) }}

{{ govukInput({
label: {
text: "County"
},
classes: "govuk-!-width-two-thirds",
id: "address-county",
name: "address-county"
}) }}

{{ govukInput({
label: {
text: "Postcode"
},
errorMessage: {
text: "Enter a valid postcode"
},
classes: "govuk-input--width-10",
id: "address-postcode",
name: "address-postcode"
}) }}

{% endcall %}

{{ govukButton({
"text": "Continue"
}) }}
Expand Down
31 changes: 29 additions & 2 deletions src/govuk/components/error-summary/error-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ ErrorSummary.prototype.getFragmentFromUrl = function (url) {
*
* Returns the first element that exists from this list:
*
* - The `<legend>` associated with the closest `<fieldset>` ancestor
* - The `<legend>` associated with the closest `<fieldset>` ancestor, as long
* as the top of it is no more than half a viewport height away from the
* bottom of the input
* - The first `<label>` that is associated with the input using for="inputId"
* - The closest parent `<label>`
*
Expand All @@ -109,7 +111,32 @@ ErrorSummary.prototype.getAssociatedLegendOrLabel = function ($input) {
var legends = $fieldset.getElementsByTagName('legend')

if (legends.length) {
return legends[0]
var $candidateLegend = legends[0]
Copy link
Contributor

@NickColley NickColley Sep 13, 2019

Choose a reason for hiding this comment

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

In theory we could write tests for this logic, using puppeteer on the demo pages you've been testing, this could be useful in the future if we (hopefully not) encounter other edge cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added to the existing tests to try and cover the two new examples that have been added to the review app.

Copy link
Contributor

Choose a reason for hiding this comment

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

Epic! Thank you


// If the input type is radio or checkbox, always use the legend if there
// is one.
if ($input.type === 'checkbox' || $input.type === 'radio') {
return $candidateLegend
}

// For other input types, only scroll to the fieldset’s legend (instead of
// the label associated with the input) if the input would end up in the
// top half of the screen.
//
// This should avoid situations where the input either ends up off the
// screen, or obscured by a software keyboard.
var legendTop = $candidateLegend.getBoundingClientRect().top
var inputRect = $input.getBoundingClientRect()

// If the browser doesn't support Element.getBoundingClientRect().height
// or window.innerHeight (like IE8), bail and just link to the label.
if (inputRect.height && window.innerHeight) {
var inputBottom = inputRect.top + inputRect.height

if (inputBottom - legendTop < window.innerHeight / 2) {
Copy link
Contributor

@NickColley NickColley Sep 13, 2019

Choose a reason for hiding this comment

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

When testing this on my machine I find that I'm always linked to the errored input for the last example despite the fieldset being able to fit all on the screen at once.

Is the issue that mobile keyboards open after the navigation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

More or less – the logic checks to see whether the input would end up entirely in the top half of the viewport, to allow for keyboards.

return $candidateLegend
}
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/govuk/components/error-summary/error-summary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ describe('Error Summary', () => {
['a group of radio buttons', 'radios', '#test-radios legend'],
['a group of checkboxes', 'checkboxes', '#test-checkboxes legend'],
['a single checkbox', 'single-checkbox', 'label[for="single-checkbox"]'],
['a conditionally revealed input', 'yes-input', '#test-conditional-reveal legend']
['a conditionally revealed input', 'yes-input', '#test-conditional-reveal legend'],
['a group of radio buttons after a particularly long heading', 'radios-big-heading', '.test-radios-big-heading-legend'],
// Rather than scrolling to the fieldset, we expect to scroll to the label
// because of the distance between the input and the fieldset
['an input within a large fieldset', 'address-postcode', 'label[for="address-postcode"]']
]

describe.each(inputTypes)('when linking to %s', (_, inputId, legendOrLabelSelector) => {
Expand Down