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 issues with conditionally revealed content when content id includes CSS syntax characters #2370

Merged
merged 3 commits into from
Oct 19, 2021

Conversation

36degrees
Copy link
Contributor

@36degrees 36degrees commented Sep 29, 2021

Because the string passed to querySelector is evaluated as a CSS selector, this can fail (throwing a JavaScript error) if the ID contains characters that have a special meaning in CSS, such as . or [] – the ID would need to be escaped for it to be evaluated correctly.

Avoid this by using document.getElementById instead, so the ID is no longer evaluated as a selector.

The downside is that we can't constrain the scope to the $module. However, we don't think this should cause any issues as the ID should be unique within the document.

Details of breaking change

  • which users are affected: anyone using conditional reveals
  • the change that’s been made: when looking for the revealed content associated with a checkbox or radio, the JavaScript will now look for it across the entire page rather than only looking within that set of radios or checkboxes
  • changes users will have to make: ensure that all IDs on a page are unique, and that checkboxes / radios with conditionally revealed content are using the right ID for their content
  • impact of users not making those changes: if a page has multiple elements with the same ID (which is technically invalid HTML) radios or checkboxes may now cause the wrong element on the page to appear / disappear when selecting a checkbox or radio

Closes #1808

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 September 29, 2021 10:23 Inactive
Copy link
Contributor

@vanitabarrett vanitabarrett left a comment

Choose a reason for hiding this comment

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

Thanks Ollie, this change makes sense to me. Probably worth updating either the PR or the issue to explain which users will be affected by this change and what they might need to do, to help Eoin draft some release notes.

@36degrees
Copy link
Contributor Author

Needs to be merged after #2255 once the target branch has automagically updated.

@36degrees
Copy link
Contributor Author

36degrees commented Oct 6, 2021

First attempt at a changelog entry:

The JavaScript for radios and checkboxes with conditionally-revealed content will now look for the conditionally-revealed content anywhere on the page rather than only looking within the same set of radios or checkboxes.

If you re-use the same id multiple times on a single page, this change could cause a radio or checkbox to hide and reveal the wrong element on the page.

Make sure that any radios or checkboxes with conditionally revealed content behave as expected. If you are seeing unexpected behaviour, make sure that the id for the revealed content is unique within the page it appears on.

Or, an alternative version which tries less hard to abstract technical details and language (which may be clearer for users who are more familiar with JavaScript, but is arguably harder to understand for everyone else):

The JavaScript for radios and checkboxes with conditionally-revealed content will now look for the conditionally-revealed content using document.getElementById rather than being scoped to the same set of radios or checkboxes.

If you re-use the same id multiple times on a single page, this change could cause a radio or checkbox to hide and reveal the wrong element on the page.

Make sure that any radios or checkboxes with conditionally revealed content behave as expected. If you are seeing unexpected behaviour, make sure that the id for the revealed content is unique within the page it appears on.

I don't know if it's also worth mentioning that using the same id for multiple elements is also invalid HTML?

@EoinShaughnessy any thoughts on the above?

@EoinShaughnessy
Copy link
Contributor

EoinShaughnessy commented Oct 6, 2021

@36degrees Changelog entry is taking shape nicely! I'd lean towards using the first, simpler version, to help any users who may be less familiar with JS. (Plus, the final PR will mention document.getElementById, right? So we're not short-changing our more JS-savvy users.)

I don't know if it's also worth mentioning that using the same id for multiple elements is also invalid HTML?

Depends, I guess. This sounds like a borderline thing between writing Design System-specific instructions and telling people how to use HTML. Maybe, by saying multiple uses could display the wrong element, we've already given enough caution.

The JavaScript for radios and checkboxes with conditionally-revealed content will now look for the conditionally-revealed content anywhere on the page rather than only looking within the same set of radios or checkboxes.

If you re-use the same id multiple times on a single page, this change could cause a radio or checkbox to hide and reveal the wrong element on the page.

Make sure that any radios or checkboxes with conditionally revealed content behave as expected. If you are seeing unexpected behaviour, make sure that the id for the revealed content is unique within the page it appears on.

First paragraph is about JS looking anywhere on the page. Second and third paragraphs are about reuse of ID. Is that ok?

Maybe combine the second and third paragraphs, and reverse their order? Something like in the suggested revision below:

JavaScript now looks across whole page for conditional reveals

On radios and checkboxes with conditionally revealed content, the JavaScript now looks for the content anywhere on the page. Before, it only looked within the same set of radios or checkboxes.

Make sure any radios or checkboxes with conditionally revealed content behave as expected. If you see unexpected behaviour, make sure the id for the revealed content is unique within the page the content is on. If you reuse the same id within a page, this duplication could cause a radio or checkbox to reveal the wrong element.

@EoinShaughnessy
Copy link
Contributor

EoinShaughnessy commented Oct 13, 2021

@36degrees Thanks for the talk-through! Had a go at slight rewording, let me know what you think!

Make sure your conditional reveals are behaving as expected

On radios and checkboxes, the JavaScript now looks within the whole page for conditionally revealed content. Before, it only looked within the same set of radios or checkboxes.

If you re-use the same id within a page, this change could cause a radio or checkbox to hide and reveal the wrong element.

Make sure any radios or checkboxes with conditionally revealed content behave as expected. If you see unexpected behaviour, make sure the revealed content's id is unique within the page the content is on.

Few questions:

  • In the title, is 'behaving as expected' a bit vague?
  • In the second paragraph, is 'hide and reveal' the right order? Isn't conditional reveals content hidden until users reveal it?
  • The second paragraph feels a bit non-sequitury in its current place. Could we add it to the end of the last paragraph, where the info kind of follows on from the sentence about making sure IDs are unique?
  • Do you think we could remove "Make sure any radios or checkboxes with conditionally revealed content behave as expected", as it just repeats the title and arguably adds little value?

@EoinShaughnessy
Copy link
Contributor

EoinShaughnessy commented Oct 13, 2021

Alternative wording, based on questions from previous comment:

Make sure your conditional reveals JavaScript looks for the right content

On radios and checkboxes, the JavaScript now looks within the whole page for conditionally revealed content. Before, it only looked within the same set of radios or checkboxes.

If you see unexpected behaviour, make sure the revealed content's id is unique within the page the content is on. Reusing the same id within a page could cause a radio or checkbox to reveal and hide the wrong element.

Base automatically changed from radio-checkboxes-tweaks to main October 14, 2021 10:40
@36degrees
Copy link
Contributor Author

@EoinShaughnessy that looks pretty good, but I really trip over the heading when reading it. I think because you naturally read 'condition reveals JavaScript' with 'reveals' being a verb, rather than 'conditional reveals Javascript' as a compound noun?

What about something like 'Make sure radios or checkboxes that conditionally reveal other questions still work as expected'?

@EoinShaughnessy
Copy link
Contributor

@36degrees Good point! Yeah, your suggestion removes that ambiguity. Want me to add something like the below to the Changelog?

Make sure components that conditionally reveal other questions still work as expected

On radios and checkboxes, the JavaScript now looks within the whole page for conditionally revealed content. Before, it only looked within the same set of radios or checkboxes.

If you see unexpected behaviour, make sure the revealed content's id is unique within the page the content is on. Reusing the same id within a page could cause a radio or checkbox to reveal and hide the wrong element.

@36degrees 36degrees force-pushed the radio-checkboxes-get-element-by-id branch from 783aae5 to 6d0c227 Compare October 14, 2021 13:35
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 14, 2021 13:35 Inactive
When checkboxes with conditional reveals are initialised, we check that the element targeted by aria-controls exists:

```
if (!controls || !$module.querySelector('#' + controls)) {
   return
 }
```

Because the string passed to `querySelector` is evaluated as a CSS selector, this can fail if the ID contains characters that have a special meaning in CSS, such as `.` or `[]` – the ID would need to be escaped for it to be evaluated correctly.

Add a failing test to cover this case.

The tests fail with:

```
Checkboxes with conditional reveals › when JavaScript is available › does not error when ID of revealed content contains special characters

    DOMException: Failed to execute 'querySelector' on 'Element': '#conditional-user.profile[contact-prefs]-2' is not a valid selector.
```

```
Radios with conditional reveals › when JavaScript is available › does not error when ID of revealed content contains special characters

    DOMException: Failed to execute 'querySelector' on 'Element': '#conditional-user.profile[contact-prefs]-2' is not a valid selector.
```
Because the string passed to `querySelector` is evaluated as a CSS selector, this can fail if the ID contains characters that have a special meaning in CSS, such as `.` or `[]` – the ID would need to be escaped for it to be evaluated correctly.

Avoid this by using `document.getElementById` instead, so the ID is no longer evaluated as a selector.

The downside is that we can't constrain the scope to the $module. However, we don't think this should cause any issues as the ID _should_ be unique within the document.

This fixes the failing tests introduced in the previous commit.
@36degrees 36degrees force-pushed the radio-checkboxes-get-element-by-id branch from 6d0c227 to 2dc30f7 Compare October 14, 2021 15:45
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 14, 2021 15:45 Inactive
CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
@EoinShaughnessy EoinShaughnessy changed the title Prevent issues with conditionally revealed content when the ID of the revealed content includes characters that have a special meaning in CSS Prevent issues with conditionally revealed content when content id includes CSS syntax characters Oct 19, 2021
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 19, 2021 12:50 Inactive
@36degrees 36degrees force-pushed the radio-checkboxes-get-element-by-id branch from 961f8b8 to 122ad88 Compare October 19, 2021 12:51
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 19, 2021 12:51 Inactive
@36degrees 36degrees force-pushed the radio-checkboxes-get-element-by-id branch from 122ad88 to 45b4b93 Compare October 19, 2021 12:51
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 19, 2021 12:52 Inactive
@36degrees 36degrees force-pushed the radio-checkboxes-get-element-by-id branch from 45b4b93 to c0a81d1 Compare October 19, 2021 12:52
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-2370 October 19, 2021 12:52 Inactive
@36degrees 36degrees merged commit 2acbfbf into main Oct 19, 2021
@36degrees 36degrees deleted the radio-checkboxes-get-element-by-id branch October 19, 2021 13:02
36degrees added a commit that referenced this pull request Oct 27, 2021
Also tweak the note for #2370 and #1963 so that we're talk about the removal of deprecated features consistently.
36degrees added a commit that referenced this pull request Oct 27, 2021
Also tweak the note for #2370 and #1963 so that we're talk about the removal of deprecated features consistently.

Co-authored-by: Eoin Shaughnessy <eoin.shaughnessy@digital.cabinet-office.gov.uk>
@vanitabarrett vanitabarrett mentioned this pull request Dec 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Conditional reveals fail if ID contains characters that have a special meaning in CSS
4 participants