-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is based on the MoJ component and existing component in Whitehall. It is to be used when users need to add similar information a couple of times, such as several featured links for an organisation. An empty field and destroy checkboxes for the existing fields are required and displayed to the user when javascript is disabled in keeping with rails conventions. When Javascript is enabled, an "add another" button is introduced to allow users to add copies of the empty field and the checkboxes replaced with "Delete" buttons which hide the fields and checks the appropriate checkbox.
- Loading branch information
Showing
8 changed files
with
476 additions
and
1 deletion.
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
app/assets/javascripts/govuk_publishing_components/components/add-another.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
window.GOVUK = window.GOVUK || {} | ||
window.GOVUK.Modules = window.GOVUK.Modules || {}; | ||
|
||
(function (Modules) { | ||
function AddAnother (module) { | ||
this.module = module | ||
this.emptyFieldset = undefined | ||
this.addAnotherButton = undefined | ||
} | ||
|
||
function createButton (textContent, additionalClass = '') { | ||
var button = document.createElement('button') | ||
button.className = 'gem-c-button govuk-button ' + additionalClass | ||
button.type = 'button' | ||
button.textContent = textContent | ||
return button | ||
} | ||
|
||
AddAnother.prototype.init = function () { | ||
this.createAddAnotherButton() | ||
this.createRemoveButtons() | ||
this.removeEmptyFieldset() | ||
this.updateFieldsetsAndButtons() | ||
} | ||
|
||
AddAnother.prototype.createAddAnotherButton = function () { | ||
this.addAnotherButton = | ||
createButton( | ||
this.module.dataset.addButtonText, | ||
'js-add-another__add-button govuk-button--secondary' | ||
) | ||
this.addAnotherButton.addEventListener('click', this.addNewFieldset.bind(this)) | ||
this.module.appendChild(this.addAnotherButton) | ||
} | ||
|
||
AddAnother.prototype.createRemoveButton = function (fieldset, removeFunction) { | ||
var removeButton = | ||
createButton( | ||
'Delete', | ||
'js-add-another__remove-button gem-c-add-another__remove-button govuk-button--warning' | ||
) | ||
removeButton.addEventListener('click', function (event) { | ||
removeFunction(event) | ||
this.updateFieldsetsAndButtons() | ||
this.addAnotherButton.focus() | ||
}.bind(this)) | ||
fieldset.appendChild(removeButton) | ||
} | ||
|
||
AddAnother.prototype.createRemoveButtons = function () { | ||
var fieldsets = | ||
document.querySelectorAll('.js-add-another__fieldset') | ||
fieldsets.forEach(function (fieldset) { | ||
this.createRemoveButton(fieldset, this.removeExistingFieldset.bind(this)) | ||
fieldset.querySelector('.js-add-another__destroy-checkbox').hidden = true | ||
}.bind(this)) | ||
} | ||
|
||
AddAnother.prototype.removeEmptyFieldset = function () { | ||
this.emptyFieldset = this.module.querySelector('.js-add-another__empty') | ||
this.emptyFieldset.remove() | ||
} | ||
|
||
AddAnother.prototype.updateFieldsetsAndButtons = function () { | ||
this.module.querySelectorAll('.js-add-another__fieldset:not([hidden]) legend') | ||
.forEach(function (legend, index) { | ||
legend.textContent = this.module.dataset.fieldsetLegend + ' ' + (index + 1) | ||
}.bind(this)) | ||
|
||
this.module.querySelector('.js-add-another__remove-button').classList.toggle( | ||
'js-add-another__remove-button--hidden', | ||
this.module.querySelectorAll('.js-add-another__fieldset:not([hidden])').length === 1 | ||
) | ||
} | ||
|
||
AddAnother.prototype.addNewFieldset = function (event) { | ||
var button = event.target | ||
var newFieldset = this.emptyFieldset.cloneNode(true) | ||
newFieldset.classList.remove('js-add-another__empty') | ||
newFieldset.classList.add('js-add-another__fieldset') | ||
this.createRemoveButton(newFieldset, this.removeNewFieldset.bind(this)) | ||
button.before(newFieldset) | ||
|
||
this.incrementAttributes(this.emptyFieldset) | ||
this.updateFieldsetsAndButtons() | ||
|
||
// Move focus to first visible field in new set | ||
newFieldset | ||
.querySelector('input:not([type="hidden"]), select, textarea') | ||
.focus() | ||
} | ||
|
||
AddAnother.prototype.removeExistingFieldset = function (event) { | ||
var fieldset = event.target.parentNode | ||
var destroyCheckbox = | ||
fieldset.querySelector('.js-add-another__destroy-checkbox input') | ||
|
||
destroyCheckbox.checked = true | ||
fieldset.hidden = true | ||
} | ||
|
||
AddAnother.prototype.removeNewFieldset = function (event) { | ||
var fieldset = event.target.parentNode | ||
fieldset.remove() | ||
} | ||
|
||
// Set attribute values for id, for and name of supplied fieldset | ||
AddAnother.prototype.incrementAttributes = function (fieldset) { | ||
var matcher = /(.*[_[])([0-9]+)([_\]].*?)$/ | ||
fieldset | ||
.querySelectorAll('label, input, select, textarea') | ||
.forEach(function (element) { | ||
['name', 'id', 'for'].forEach(function (attribute) { | ||
var value = element.getAttribute(attribute) | ||
var matched = matcher.exec(value) | ||
if (!matched) return | ||
var index = parseInt(matched[2], 10) + 1 | ||
element.setAttribute(attribute, matched[1] + index + matched[3]) | ||
}) | ||
}) | ||
} | ||
|
||
Modules.AddAnother = AddAnother | ||
})(window.GOVUK.Modules) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
app/assets/stylesheets/govuk_publishing_components/components/_add-another.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
@import "govuk_publishing_components/individual_component_support"; | ||
@import "govuk/components/button/button"; | ||
@import "govuk/components/fieldset/fieldset"; | ||
|
||
.gem-c-add-another__remove-button { | ||
margin-top: govuk-spacing(6); | ||
margin-bottom: 0; | ||
} | ||
|
||
.js-add-another__remove-button--hidden { | ||
display: none; | ||
} |
29 changes: 29 additions & 0 deletions
29
app/views/govuk_publishing_components/components/_add_another.html.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<% | ||
add_gem_component_stylesheet("add-another") | ||
items ||= [] | ||
empty ||= "" | ||
fieldset_legend ||= "" | ||
add_button_text ||= "Add another" | ||
%> | ||
|
||
<div data-module="add-another" class="gem-c-add-another" data-add-button-text="<%= add_button_text %>" data-fieldset-legend="<%= fieldset_legend %>"> | ||
<% items.each_with_index do |item, index| %> | ||
<%= render "govuk_publishing_components/components/fieldset", { | ||
classes: "js-add-another__fieldset", | ||
legend_text: "#{fieldset_legend} #{index + 1}", | ||
heading_size: "m" | ||
} do %> | ||
<div class="js-add-another__destroy-checkbox"> | ||
<%= item[:destroy_checkbox] %> | ||
</div> | ||
<%= item[:fields] %> | ||
<% end %> | ||
<% end %> | ||
<%= render "govuk_publishing_components/components/fieldset", { | ||
classes: "js-add-another__empty", | ||
legend_text: "#{fieldset_legend} #{items.length + 1}", | ||
heading_size: "m" | ||
} do %> | ||
<%= empty %> | ||
<% end %> | ||
</div> |
37 changes: 37 additions & 0 deletions
37
app/views/govuk_publishing_components/components/docs/add_another.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Add another (experimental) | ||
description: The "add another" component lets users input multiple values for a set of form fields. | ||
body: | | ||
This component is currently experimental because more research is needed to validate it. | ||
Applications using this component must include a deletion checkbox in the rendered repeating items so that users can remove | ||
items from the list in the event that Javascript is not enabled. See the examples below for how to do this. | ||
accessibility_criteria: | | ||
The form controls within the fieldsets must be fully accessible as per the design system guidance for each of the form | ||
control components. | ||
uses_component_wrapper_helper: false | ||
govuk_frontend_components: | ||
- button | ||
examples: | ||
default: | ||
data: | ||
fieldset_legend: "Person" | ||
add_button_text: "Add another person" | ||
items: | ||
- fields: > | ||
<div class="govuk-form-group"> | ||
<label for="person_0_name" class="gem-c-label govuk-label">Full name</label> | ||
<input class="gem-c-input govuk-input" id="person_0_name" name="person[0]name"> | ||
</div> | ||
destroy_checkbox: > | ||
<div class="govuk-checkboxes" data-module="govuk-checkboxes" data-govuk-checkboxes-init=""> | ||
<div class="govuk-checkboxes__item"> | ||
<input type="checkbox" name="person[0][_destroy]" id="person_0__destroy" class="govuk-checkboxes__input"> | ||
<label for="person_0__destroy" class="govuk-label govuk-checkboxes__label">Delete</label> | ||
</div> | ||
</div> | ||
empty: | ||
<div class="govuk-form-group"> | ||
<label for="person_1_name" class="gem-c-label govuk-label">Full name</label> | ||
<input class="gem-c-input govuk-input" id="person_1_name" name="person[1]name"> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require "rails_helper" | ||
|
||
describe "Add another", type: :view do | ||
def component_name | ||
"add_another" | ||
end | ||
|
||
def default_items | ||
[ | ||
{ | ||
fields: sanitize("<div class=\"item1\">item1</div>"), | ||
destroy_checkbox: sanitize("<input type=\"checkbox\" />"), | ||
}, | ||
{ | ||
fields: sanitize("<div class=\"item2\">item2</div>"), | ||
destroy_checkbox: sanitize("<input type=\"checkbox\" />"), | ||
}, | ||
] | ||
end | ||
|
||
it "renders a wrapper element" do | ||
render_component(items: default_items) | ||
|
||
assert_select "div.gem-c-add-another[data-module='add-another']" | ||
end | ||
|
||
it "renders the items provided" do | ||
empty = "" | ||
render_component({ items: default_items, empty: }) | ||
|
||
assert_select "div.js-add-another__fieldset .item1" | ||
assert_select "div.js-add-another__fieldset .item2" | ||
end | ||
|
||
it "renders a destroy checkbox for each item" do | ||
empty = "" | ||
render_component({ items: default_items, empty: }) | ||
|
||
assert_select "div.js-add-another__fieldset .js-add-another__destroy-checkbox", count: 2 | ||
end | ||
|
||
it "renders the empty item" do | ||
empty = sanitize("<div class=\"empty\">empty</div>") | ||
render_component({ items: default_items, empty: }) | ||
|
||
assert_select ".js-add-another__empty div.empty" | ||
end | ||
end |
Oops, something went wrong.