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

Restore accessible autocomplete component #2481

Closed
wants to merge 5 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-env jquery */
/* global accessibleAutocomplete */
// = require accessible-autocomplete/dist/accessible-autocomplete.min.js

window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
function AccessibleAutocomplete ($module) {
this.$module = $module
this.selectElement = this.$module.querySelector('select')
}

AccessibleAutocomplete.prototype.init = function () {
var configOptions = {
selectElement: this.selectElement,
autoselect: true,
confirmOnBlur: true,
preserveNullOptions: true, // https://github.com/alphagov/accessible-autocomplete#null-options
defaultValue: '',
govukModule: this // attach this instance of the module so we can access it in onConfirm
}

configOptions.onConfirm = this.onConfirm
new accessibleAutocomplete.enhanceSelectElement(configOptions) // eslint-disable-line no-new, new-cap
this.autoCompleteInput = this.$module.querySelector('.autocomplete__input')
if (this.autoCompleteInput) {
this.autoCompleteInput.addEventListener('keyup', this.handleKeyup.bind(this))
}
}

// custom onConfirm function because will likely need future expansion e.g. tracking
AccessibleAutocomplete.prototype.onConfirm = function (value) {
// onConfirm fires on selecting an option
// also fires when blurring the input, which then provides `value` as undefined
// so we only update the hidden select if there's a value, and handle clearing it separately
if (typeof value !== 'undefined') {
// the accessible-autocomplete doesn't update the hidden select if an onConfirm function is supplied
// https://github.com/alphagov/accessible-autocomplete/issues/322
var options = this.selectElement.querySelectorAll('option')
for (var i = 0; i < options.length; i++) {
var text = options[i].textContent
if (text === value) {
this.govukModule.setSelectOption(options[i])
break
}
}
}
}

// seems to be a bug in the accessible autocomplete where clearing the input
// doesn't update the select, so we have to do this
AccessibleAutocomplete.prototype.handleKeyup = function (e) {
var value = this.autoCompleteInput.value

if (value.length === 0) {
this.clearSelect()
}
}

AccessibleAutocomplete.prototype.setSelectOption = function (option) {
var value = option.value
this.selectElement.value = value
window.GOVUK.triggerEvent(this.selectElement, 'change')
}

AccessibleAutocomplete.prototype.clearSelect = function () {
this.selectElement.value = ''
window.GOVUK.triggerEvent(this.selectElement, 'change')
}

Modules.AccessibleAutocomplete = AccessibleAutocomplete
})(window.GOVUK.Modules)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ $govuk-new-link-styles: true;
@import "govuk/components/all";

// components
@import "components/accessible-autocomplete";
@import "components/accordion";
@import "components/action-link";
@import "components/attachment";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import url(asset-path("accessible-autocomplete/dist/accessible-autocomplete.min.css")); // stylelint-disable function-url-quotes

.gem-c-accessible-autocomplete {
.autocomplete__input {
z-index: 1;
}

.autocomplete__option {
@include govuk-font(19);
min-height: 1.3em; // this ensures a blank item has a height
}

.autocomplete__list .autocomplete__option {
padding: 5px 6px;

&:before {
position: relative;
top: 3px;
padding-top: 2px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<%
id ||= "autocomplete-#{SecureRandom.hex(4)}"
label ||= nil
data_attributes ||= nil
options ||= []
selected_option ||= nil

classes = %w(gem-c-accessible-autocomplete govuk-form-group)
%>
<% if label && options.any? %>
<%= tag.div class: classes, data: { module: "accessible-autocomplete" } do %>
<%=
render "govuk_publishing_components/components/label", {
html_for: id
}.merge(label.symbolize_keys)
%>

<%=
select_tag(
id,
options_for_select(options, selected_option),
class: "govuk-select",
data: data_attributes
)
%>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Accessible autocomplete (experimental)
description: An autocomplete component, built to be accessible.
body: |
This component uses the [Accessible Autocomplete](https://github.com/alphagov/accessible-autocomplete) code to progressively enhance a hidden select element (see [Autocomplete examples](https://alphagov.github.io/accessible-autocomplete/examples) here). It depends upon the [label component](https://github.com/component-guide/label).

If Javascript is disabled, the component appears as a select box, so the user can still select an option.
accessibility_criteria: |
[Accessibility acceptance criteria](https://github.com/alphagov/accessible-autocomplete/blob/master/accessibility-criteria.md)
examples:
default:
data:
label:
text: 'Countries'
options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us'], ['The Separate Customs Territory of Taiwan, Penghu, Kinmen, and Matsu (Chinese Taipei)', 'tw']]
with_unique_identifier:
data:
id: 'unique-autocomplete'
label:
text: 'Countries'
options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us']]
with_selected_option_chosen:
data:
id: 'selected-option-chosen-autocomplete'
label:
text: 'Countries'
options: [['', ''], ['France', 'fr'], ['Germany', 'de'], ['Sweden', 'se'], ['Switzerland', 'ch'], ['United Kingdom', 'gb'], ['United States', 'us']]
selected_option: ['United Kingdom', 'gb']
1 change: 1 addition & 0 deletions config/initializers/assets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

# GOV.UK Frontend assets
Rails.application.config.assets.precompile += %w[
accessible-autocomplete/dist/accessible-autocomplete.min.css
govuk-logotype-crown.png
favicon.ico
govuk-opengraph-image.png
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"extends": "stylelint-config-gds/scss"
},
"dependencies": {
"accessible-autocomplete": "git://github.com/alphagov/accessible-autocomplete.git",
"axe-core": "^3.5.4",
"govuk-frontend": "^3.14.0",
"jquery": "1.12.4",
Expand Down
55 changes: 55 additions & 0 deletions spec/components/accessible_autocomplete_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require "rails_helper"

describe "AccessibleAutocomplete", type: :view do
def component_name
"accessible_autocomplete"
end

it "renders select element" do
render_component(
id: "basic-autocomplete",
label: { text: "Countries" },
options: [["United Kingdom", "gb"], ["United States", "us"]],
)

assert_select ".govuk-label", text: "Countries", for: "basic-autocomplete"
assert_select "select#basic-autocomplete"
assert_select "select[multiple]", false
assert_select "select#basic-autocomplete option[value=gb]"
assert_select "select#basic-autocomplete option[value=gb]", text: "United Kingdom"
assert_select "select#basic-autocomplete option[value=us]"
assert_select "select#basic-autocomplete option[value=us]", text: "United States"
end

it "renders select element with selected value" do
render_component(
id: "basic-autocomplete",
label: { text: "Countries" },
options: [["United Kingdom", "gb"], ["United States", "us"]],
selected_option: ["United States", "us"],
)

assert_select ".govuk-label", text: "Countries", for: "basic-autocomplete"
assert_select "select#basic-autocomplete"
assert_select "select#basic-autocomplete option[value=gb]"
assert_select "select#basic-autocomplete option[value=us][selected]"
end

it "does not render when no data is specified" do
assert_empty render_component({})
end

it "does not render when no label is specified" do
assert_empty render_component(
id: "basic-autocomplete",
options: [["United Kingdom", "gb"], ["United States", "us"]],
)
end

it "does not render when no options are specified" do
assert_empty render_component(
id: "basic-autocomplete",
label: { text: "Countries" },
)
end
end
89 changes: 89 additions & 0 deletions spec/javascripts/components/accessible-autocomplete-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-env jasmine, jquery */
/* global GOVUK */

describe('An accessible autocomplete component', function () {
var fixture, select

function loadAutocompleteComponent () {
fixture = document.createElement('div')
document.body.appendChild(fixture)
fixture.innerHTML = html
select = fixture.querySelector('select')
var autocomplete = new GOVUK.Modules.AccessibleAutocomplete(fixture.querySelector('.gem-c-accessible-autocomplete'))
autocomplete.init()
}

var html =
'<div class="gem-c-accessible-autocomplete">' +
'<select id="test" class="govuk-select">' +
'<option value=""></option>' +
'<option value="mo">Moose</option>' +
'<option value="de">Deer</option>' +
'</select>' +
'</div>'

// the autocomplete onConfirm function fires after the tests run unless we put
// in a timeout like this - makes the tests a bit verbose unfortunately
function testAsyncWithDeferredReturnValue () {
var deferred = $.Deferred()

setTimeout(function () {
deferred.resolve()
}, 1)

return deferred.promise()
}

afterEach(function () {
fixture.remove()
})

describe('updates the hidden select when', function () {
beforeEach(function (done) {
loadAutocompleteComponent()
var input = fixture.querySelector('.autocomplete__input')
input.value = 'Moose'

// the autocomplete is complex enough that all of these
// events are necessary to simulate user input
// need to use triggerEvent as direct e.g. .click() doesn't work headless
window.GOVUK.triggerEvent(input, 'focus')
window.GOVUK.triggerEvent(input, 'keyup')
window.GOVUK.triggerEvent(input, 'click')
window.GOVUK.triggerEvent(input, 'blur')

testAsyncWithDeferredReturnValue().done(function () {
done()
})
})

it('an option is selected', function () {
expect(select.value).toEqual('mo')
})
})

describe('updates the hidden select when', function () {
beforeEach(function (done) {
loadAutocompleteComponent()

var input = fixture.querySelector('.autocomplete__input')
input.value = 'Deer'
select.value = 'de'
window.GOVUK.triggerEvent(select, 'change')

input.value = ''
window.GOVUK.triggerEvent(input, 'focus')
window.GOVUK.triggerEvent(input, 'keyup')
window.GOVUK.triggerEvent(input, 'click')
window.GOVUK.triggerEvent(input, 'blur')

testAsyncWithDeferredReturnValue().done(function () {
done()
})
})

it('the input is cleared', function () {
expect(select.value).toEqual('')
})
})
})
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"

"accessible-autocomplete@git://github.com/alphagov/accessible-autocomplete.git":
version "2.0.3"
resolved "git://github.com/alphagov/accessible-autocomplete.git#935f0d43aea1c606e6b38985e3fe7049ddbe98be"
dependencies:
preact "^8.3.1"

acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
Expand Down Expand Up @@ -3254,6 +3260,11 @@ postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.
source-map "^0.6.1"
supports-color "^6.1.0"

preact@^8.3.1:
version "8.5.3"
resolved "https://registry.yarnpkg.com/preact/-/preact-8.5.3.tgz#78c2a5562fcecb1fed1d0055fa4ac1e27bde17c1"
integrity sha512-O3kKP+1YdgqHOFsZF2a9JVdtqD+RPzCQc3rP+Ualf7V6rmRDchZ9MJbiGTT7LuyqFKZqlHSOyO/oMFmI2lVTsw==

prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
Expand Down