diff --git a/app/assets/javascripts/collapsibleCheckboxes.js b/app/assets/javascripts/collapsibleCheckboxes.js
new file mode 100644
index 000000000..97957b3d1
--- /dev/null
+++ b/app/assets/javascripts/collapsibleCheckboxes.js
@@ -0,0 +1,142 @@
+(function (Modules) {
+ "use strict";
+
+ Modules.CollapsibleCheckboxes = function() {
+ const _focusTextElement = ($el) => {
+ $el
+ .attr('tabindex', '-1')
+ .focus();
+ };
+
+ this.start = function(component) {
+ this.$formGroup = $(component);
+ this.$fieldset = this.$formGroup.find('fieldset');
+ this.$checkboxes = this.$fieldset.find('input[type=checkbox]');
+ this.summary.$el = this.$formGroup.find('.selection-summary');
+ this.fieldLabel = this.$formGroup.data('fieldLabel');
+ this.total = this.$checkboxes.length;
+
+ this.addHeadingHideLegend();
+ this.addFooterAndDoneButton();
+
+ // create summary from component pieces and match text to current selection
+ this.summary.$el = this.summary.create(this.getSelection(), this.total);
+ this.summary.update(this.getSelection(), this.total, this.fieldLabel);
+ this.$fieldset.before(this.summary.$el);
+
+ // hide checkboxes
+ this.$fieldset.hide();
+ this.expanded = false;
+
+ // set semantic relationships with aria attributes
+ this.addARIAToButtons();
+
+ this.bindEvents();
+ };
+ this.getSelection = function() { return this.$checkboxes.filter(':checked').length; };
+ this.addHeadingHideLegend = function() {
+ const headingLevel = this.$formGroup.data('heading-level') || '2';
+ const $legend = this.$fieldset.find('legend');
+
+ this.$heading = $(`
+ +
`); + + this.$text = $el.find('span'); + this.$changeButton = $el.find('button'); + + return $el; + }, + update: function(selection, total, field) { + let template; + + if (selection === total) { + template = 'all'; + } else if (selection > 0) { + template = 'some'; + } else { + template = 'none'; + } + + this.$text.html(this.templates[template](selection, total, field)); + } + }; + this.addFooterAndDoneButton = function () { + this.$footer = $(``); + + this.$doneButton = this.$footer.find('.button'); + this.$fieldset.append(this.$footer); + }; + this.addARIAToButtons = function () { + const aria = { + 'aria-expanded': this.expanded, + 'aria-controls': this.$fieldset.attr('id') + }; + + this.summary.$changeButton.attr(aria); + this.$doneButton.attr(aria); + }; + this.expand = function(e) { + if (e !== undefined) { e.preventDefault(); } + + if (!this.expanded) { + this.$fieldset.show(); + this.summary.$changeButton.attr('aria-expanded', true); + this.$doneButton.attr('aria-expanded', true); + this.expanded = true; + } + + // shift focus whether expanded or not + _focusTextElement(this.$fieldset); + }; + this.collapse = function(e) { + if (e !== undefined) { e.preventDefault(); } + + if (this.expanded) { + this.$fieldset.hide(); + this.summary.$changeButton.attr('aria-expanded', false); + this.$doneButton.attr('aria-expanded', false); + this.expanded = false; + } + + // shift focus whether expanded or not + _focusTextElement(this.summary.$text); + }; + this.handleSelection = function(e) { + this.summary.update(this.getSelection(), this.total, this.fieldLabel); + }; + this.bindEvents = function() { + const self = this; + + this.summary.$changeButton.on('click', this.expand.bind(this)); + this.$doneButton.on('click', this.collapse.bind(this)); + this.$checkboxes.on('click', this.handleSelection.bind(this)); + + // take summary out of tab order when focus moves + this.summary.$el.on('blur', (e) => $(this).attr('tabindex', '-1')); + }; + }; + +}(window.GOVUK.Modules)); diff --git a/app/assets/stylesheets/components/checkboxes.scss b/app/assets/stylesheets/components/checkboxes.scss index 40ca600d4..6e6b5414c 100644 --- a/app/assets/stylesheets/components/checkboxes.scss +++ b/app/assets/stylesheets/components/checkboxes.scss @@ -1,3 +1,31 @@ +.selection-summary { + + .selection-summary__text { + display: inline-block; + padding: .526315em .789473em .263157em 40px; + padding-left: 40px; + background-image: file-url('folder-black-bold.svg'); + background-repeat: no-repeat; + background-size: auto 20px; + background-position: 0px 8px; + + &:focus { + outline: none; + } + } + + @include ie-lte(8) { + background-image: file-url('folder-blue-bold.png'); + } + + // revert full-width button for smaller screens + .button { + display: inline-block; + width: auto; + } + +} + .checkboxes-nested { margin-bottom: 10px; @@ -42,3 +70,27 @@ } } + +.selection-footer { + clear: both; + padding-top: $gutter / 6; + + @include media(tablet) { + padding-top: $gutter / 3; + } + + .button-secondary { + // revert full-width button for smaller screens + display: inline-block; + width: auto; + } +} + +// styles specific to the collapsible checkboxes module +[data-module=collapsible-checkboxes] { + + fieldset:focus { + outline: none; + } + +} diff --git a/app/templates/components/checkbox.html b/app/templates/components/checkbox.html index 51594eea6..3647c4f1c 100644 --- a/app/templates/components/checkbox.html +++ b/app/templates/components/checkbox.html @@ -19,13 +19,13 @@ {% endmacro %} -{% macro checkboxes_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False) %} - {{ select_nested(field, child_map, hint, disable, option_hints, hide_legend, input="checkbox") }} +{% macro checkboxes_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}, legend_style="text") %} + {{ select_nested(field, child_map, hint, disable, option_hints, hide_legend, collapsible_opts, legend_style, input="checkbox") }} {% endmacro %} -{% macro checkboxes(field, hint=None, disable=[], option_hints={}, hide_legend=False) %} - {{ select(field, hint, disable, option_hints, hide_legend, input="checkbox") }} +{% macro checkboxes(field, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}) %} + {{ select(field, hint, disable, option_hints, hide_legend, collapsible_opts, input="checkbox") }} {% endmacro %} diff --git a/app/templates/components/select-input.html b/app/templates/components/select-input.html index b3cfbb9e4..a42f00a9f 100644 --- a/app/templates/components/select-input.html +++ b/app/templates/components/select-input.html @@ -1,6 +1,6 @@ -{% macro select(field, hint=None, disable=[], option_hints={}, hide_legend=False, input="radio") %} +{% macro select(field, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}, legend_style="text", input="radio") %} {% call select_wrapper( - field, hint, disable, option_hints, hide_legend + field, hint, disable, option_hints, hide_legend, collapsible_opts, legend_style ) %} {% for option in field %} {{ select_input(option, disable, option_hints, input=input) }} @@ -24,9 +24,9 @@ {% endmacro %} -{% macro select_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False, input="radio") %} +{% macro select_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}, legend_style="text", input="radio") %} {% call select_wrapper( - field, hint, disable, option_hints, hide_legend + field, hint, disable, option_hints, hide_legend, collapsible_opts, legend_style ) %}