Skip to content

Commit

Permalink
Allow selecting options by passing current values
Browse files Browse the repository at this point in the history
At the minute the only way to pre-select or check specific items when using the Nunjucks macros for these components is to pass a boolean flag for each item, which then maps directly to the checked / selected HTML attribute for the <input> / <option>.

This means the user has to do the work to map the previous form data back to a boolean, resulting in code that might look something like this:

```
{{ govukSelect({
    id: "sort",
    name: "sort",
    label: {
        text: "Sort by"
    },
    items: [
        {
            value: "published",
            text: "Recently published",
            selected: (data['sort'] == "published")
        },
        {
            value: "updated",
            text: "Recently updated",
            selected: (data['sort'] == "updated")
        },
        {
            value: "views",
            text: "Most views",
            selected: (data['sort'] == "views")
        },
        {
            value: "comments",
            text: "Most comments",
            selected: (data['sort'] == "comments")
        }
    ]
}) }}
```

This is prone to introducing errors (especially when prototyping) – for example, any changes to the name of the field or the value of the item also need corresponding changes made to the boolean logic, potentially in multiple places.

Allow the user to pass the current value (or array of values, for checkboxes), and using that to work out which item(s) should be checked or selected.

The above example can then be done like this:

```
{{ govukSelect({
    id: "sort",
    name: "sort",
    label: {
        text: "Sort by"
    },
    items: [
        {
            value: "published",
            text: "Recently published"
        },
        {
            value: "updated",
            text: "Recently updated"
        },
        {
            value: "views",
            text: "Most views"
        },
        {
            value: "comments",
            text: "Most comments"
        }
    ],
    value: data[‘sort’]
}) }}
```
  • Loading branch information
36degrees committed May 20, 2022
1 parent 01ce0ef commit 1ecc56b
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 10 deletions.
28 changes: 27 additions & 1 deletion src/govuk/components/checkboxes/checkboxes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ params:
- name: checked
type: boolean
required: false
description: If `true`, checkbox will be checked.
description: If `true`, the checkbox will be checked when the page loads.
- name: conditional
type: boolean
required: false
Expand All @@ -98,6 +98,10 @@ params:
type: object
required: false
description: HTML attributes (for example data attributes) to add to the checkbox input tag.
- name: values
type: array
required: false
description: Array of values for checkboxes which should be checked when the page loads. This can be used as an alternative to setting the `checked` option on each individual item.
- name: classes
type: string
required: false
Expand Down Expand Up @@ -129,6 +133,28 @@ examples:
- value: other
text: Citizen of another country

- name: with pre-checked values
data:
name: nationality
items:
- value: british
text: British
- value: irish
text: Irish
- value: other
text: Citizen of another country
conditional:
html: |
<div class="govuk-form-group">
<label class="govuk-label" for="input-example">
Country
</label>
<input class="govuk-input" id="other-country" name="other-country" type="text" value="Ravka">
</div>
values:
- british
- other

- name: with divider and None
data:
name: with-divider-and-none
Expand Down
5 changes: 3 additions & 2 deletions src/govuk/components/checkboxes/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@
{%- if item.divider %}
<div class="govuk-checkboxes__divider">{{ item.divider }}</div>
{%- else %}
{% set isChecked = item.checked or (params.values and item.value in params.values) %}
{% set hasHint = true if item.hint.text or item.hint.html %}
{% set itemHintId = id + "-item-hint" if hasHint else "" %}
{% set itemDescribedBy = describedBy if not hasFieldset else "" %}
{% set itemDescribedBy = (itemDescribedBy + " " + itemHintId) | trim %}
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" id="{{ id }}" name="{{ name }}" type="checkbox" value="{{ item.value }}"
{{-" checked" if item.checked }}
{{-" checked" if isChecked }}
{{-" disabled" if item.disabled }}
{%- if item.conditional.html %} data-aria-controls="{{ conditionalId }}"{% endif -%}
{%- if item.behaviour %} data-behaviour="{{ item.behaviour }}"{% endif -%}
Expand All @@ -93,7 +94,7 @@
{% endif %}
</div>
{% if item.conditional.html %}
<div class="govuk-checkboxes__conditional{% if not item.checked %} govuk-checkboxes__conditional--hidden{% endif %}" id="{{ conditionalId }}">
<div class="govuk-checkboxes__conditional{% if not isChecked %} govuk-checkboxes__conditional--hidden{% endif %}" id="{{ conditionalId }}">
{{ item.conditional.html | safe }}
</div>
{% endif %}
Expand Down
21 changes: 21 additions & 0 deletions src/govuk/components/checkboxes/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ describe('Checkboxes', () => {
expect($lastInput.attr('checked')).toEqual('checked')
})

it('checks the checkboxes in values', () => {
const $ = render('checkboxes', examples['with pre-checked values'])

const $component = $('.govuk-checkboxes')
const $british = $component.find('input[value="british"]')
expect($british.attr('checked')).toEqual('checked')

const $other = $component.find('input[value="other"]')
expect($other.attr('checked')).toEqual('checked')
})

describe('when they include attributes', () => {
it('it renders the attributes', () => {
const $ = render('checkboxes', examples['items with attributes'])
Expand Down Expand Up @@ -241,6 +252,16 @@ describe('Checkboxes', () => {
expect($firstConditional.hasClass('govuk-checkboxes__conditional--hidden')).toBeFalsy()
})

it('visible when checked with pre-checked values', () => {
const $ = render('checkboxes', examples['with pre-checked values'])

const $component = $('.govuk-checkboxes')

const $firstConditional = $component.find('.govuk-checkboxes__conditional').first()
expect($firstConditional.text().trim()).toContain('Country')
expect($firstConditional.hasClass('govuk-checkboxes__conditional--hidden')).toBeFalsy()
})

it('with association to the input they are controlled by', () => {
const $ = render('checkboxes', examples['with conditional items'])

Expand Down
47 changes: 45 additions & 2 deletions src/govuk/components/radios/radios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ params:
- name: checked
type: boolean
required: false
description: If `true`, radio will be checked.
description: If `true`, the radio will be checked when the page loads.
- name: conditional
type: string
required: false
Expand All @@ -86,6 +86,10 @@ params:
type: object
required: false
description: HTML attributes (for example data attributes) to add to the radio input tag.
- name: value
type: string
required: false
description: The value for the radio which should be checked when the page loads. This can be used as an alternative to setting the `checked` option on each individual item.
- name: classes
type: string
required: false
Expand Down Expand Up @@ -116,7 +120,6 @@ examples:
text: Yes
- value: no
text: No
checked: true

- name: inline
data:
Expand Down Expand Up @@ -380,6 +383,46 @@ examples:
<label class="govuk-label" for="contact-text-message">Mobile phone number</label>
<input class="govuk-input govuk-!-width-one-third" name="contact-text-message" type="text" id="contact-text-message">
- name: prechecked
data:
name: example-default
hint:
text: This includes changing your last name or spelling your name differently.
items:
- value: yes
text: Yes
- value: no
text: No
checked: true

- name: with conditional items and pre-checked value
data:
idPrefix: 'how-contacted-checked'
name: 'how-contacted-checked'
fieldset:
legend:
text: How do you want to be contacted?
items:
- value: email
text: Email
conditional:
html: |
<label class="govuk-label" for="context-email">Email</label>
<input class="govuk-input govuk-!-width-one-third" name="context-email" type="text" id="context-email">
- value: phone
text: Phone
conditional:
html: |
<label class="govuk-label" for="contact-phone">Phone number</label>
<input class="govuk-input govuk-!-width-one-third" name="contact-phone" type="text" id="contact-phone">
- value: text
text: Text message
conditional:
html: |
<label class="govuk-label" for="contact-text-message">Mobile phone number</label>
<input class="govuk-input govuk-!-width-one-third" name="contact-text-message" type="text" id="contact-text-message">
value: text

- name: with optional form-group classes showing group error
data:
idPrefix: 'how-contacted-2'
Expand Down
5 changes: 3 additions & 2 deletions src/govuk/components/radios/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@
{%- if item.divider %}
<div class="govuk-radios__divider">{{ item.divider }}</div>
{%- else %}
{% set isChecked = item.checked or (params.value and item.value == params.value) %}
{% set hasHint = true if item.hint.text or item.hint.html %}
{% set itemHintId = id + '-item-hint' %}
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="{{ id }}" name="{{ params.name }}" type="radio" value="{{ item.value }}"
{{-" checked" if item.checked }}
{{-" checked" if isChecked }}
{{-" disabled" if item.disabled }}
{%- if item.conditional.html %} data-aria-controls="{{ conditionalId }}"{% endif -%}
{%- if hasHint %} aria-describedby="{{ itemHintId }}"{% endif -%}
Expand All @@ -83,7 +84,7 @@
{% endif %}
</div>
{% if item.conditional.html %}
<div class="govuk-radios__conditional{% if not item.checked %} govuk-radios__conditional--hidden{% endif %}" id="{{ conditionalId }}">
<div class="govuk-radios__conditional{% if not isChecked %} govuk-radios__conditional--hidden{% endif %}" id="{{ conditionalId }}">
{{ item.conditional.html | safe }}
</div>
{% endif %}
Expand Down
18 changes: 17 additions & 1 deletion src/govuk/components/radios/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,21 @@ describe('Radios', () => {
})

it('render checked', () => {
const $ = render('radios', examples.default)
const $ = render('radios', examples.prechecked)

const $component = $('.govuk-radios')
const $lastInput = $component.find('.govuk-radios__item:last-child input')
expect($lastInput.attr('checked')).toEqual('checked')
})

it('checks the radio that matches value', () => {
const $ = render('radios', examples['with conditional items and pre-checked value'])

const $component = $('.govuk-radios')
const $lastInput = $component.find('input[value="text"]')
expect($lastInput.attr('checked')).toEqual('checked')
})

describe('when they include attributes', () => {
it('it renders the attributes', () => {
const $ = render('radios', examples['items with attributes'])
Expand Down Expand Up @@ -177,6 +185,14 @@ describe('Radios', () => {
expect($hiddenConditional.hasClass('govuk-radios__conditional--hidden')).toBeTruthy()
})

it('visible when checked because of checkedValue', () => {
const $ = render('radios', examples['with conditional items and pre-checked value'])

const $conditional = $('.govuk-radios__conditional').last()
expect($conditional.text()).toContain('Mobile phone number')
expect($conditional.hasClass('govuk-radios__conditional--hidden')).toBeFalsy()
})

it('visible by default when checked', () => {
const $ = render('radios', examples['with conditional item checked'])

Expand Down
25 changes: 24 additions & 1 deletion src/govuk/components/select/select.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ params:
- name: selected
type: boolean
required: false
description: Sets the option as the selected.
description: Sets the option as selected when the page loads.
- name: disabled
type: boolean
required: false
Expand All @@ -32,6 +32,10 @@ params:
type: object
required: false
description: HTML attributes (for example data attributes) to add to the option.
- name: value
type: string
required: false
description: Value for the option which should be selected. This can be used as an alternative to setting the `selected` option on each individual item.
- name: describedBy
type: string
required: false
Expand Down Expand Up @@ -88,6 +92,25 @@ examples:
value: 3
text: GOV.UK frontend option 3
disabled: true
- name: with selected value
data:
id: select-1
name: select-1
label:
text: Label text goes here
items:
-
value: 1
text: GOV.UK frontend option 1
-
value: 2
text: GOV.UK frontend option 2
selected: true
-
value: 3
text: GOV.UK frontend option 3
disabled: true
value: 3
- name: with hint text and error message
data:
id: select-2
Expand Down
2 changes: 1 addition & 1 deletion src/govuk/components/select/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{% for item in params.items %}
{% if item %}
<option value="{{ item.value }}"
{{-" selected" if item.selected }}
{{-" selected" if item.selected or (params.value and item.value == params.value) }}
{{-" disabled" if item.disabled }}
{%- for attribute, value in item.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}>{{ item.text }}</option>
{% endif %}
Expand Down
7 changes: 7 additions & 0 deletions src/govuk/components/select/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ describe('Select', () => {
expect($selectedItem.attr('selected')).toBeTruthy()
})

it('selects options using selected value', () => {
const $ = render('select', examples['with selected value'])

const $selectedItem = $('option[value="2"]')
expect($selectedItem.attr('selected')).toBeTruthy()
})

it('renders item with disabled', () => {
const $ = render('select', examples.default)

Expand Down

0 comments on commit 1ecc56b

Please sign in to comment.