mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-02 20:59:39 -04:00
Split module out into objects for each component
Is clearer than just having one large hierarchical object and makes the relationships between the module and its components clearer.
This commit is contained in:
@@ -1,150 +1,164 @@
|
|||||||
(function (Modules) {
|
(function (Modules) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Modules.CollapsibleCheckboxes = function() {
|
function Summary (module) {
|
||||||
const _focusTextElement = ($el) => {
|
this.module = module;
|
||||||
$el
|
this.$el = module.$formGroup.find('.selection-summary');
|
||||||
.attr('tabindex', '-1')
|
this.fieldLabel = module.fieldLabel;
|
||||||
.focus();
|
this.total = module.total;
|
||||||
};
|
this.addContent();
|
||||||
|
this.update(module.getSelection());
|
||||||
|
}
|
||||||
|
Summary.prototype.templates = {
|
||||||
|
all: (selection, total, field) => `All ${field}s`,
|
||||||
|
some: (selection, total, field) => `${selection} of ${total} ${field}s`,
|
||||||
|
none: (selection, total, field) => ({
|
||||||
|
"folder": "No folders (only templates outside a folder)",
|
||||||
|
"team member": "No team members (only you)"
|
||||||
|
}[field] || `No ${field}s`)
|
||||||
|
};
|
||||||
|
Summary.prototype.addContent = function() {
|
||||||
|
this.$text = $(`<p class="selection-summary__text" />`);
|
||||||
|
|
||||||
this.start = function(component) {
|
if (this.fieldLabel === 'folder') { this.$text.addClass('selection-summary__text--folders'); }
|
||||||
this.$formGroup = $(component);
|
|
||||||
this.$fieldset = this.$formGroup.find('fieldset');
|
|
||||||
this.$checkboxes = this.$fieldset.find('input[type=checkbox]');
|
|
||||||
this.fieldLabel = this.$formGroup.data('fieldLabel');
|
|
||||||
this.total = this.$checkboxes.length;
|
|
||||||
this.legendText = this.$fieldset.find('legend').text().trim();
|
|
||||||
this.expanded = false;
|
|
||||||
|
|
||||||
this.addHeadingHideLegend();
|
this.$el.append(this.$text);
|
||||||
|
};
|
||||||
|
Summary.prototype.update = function(selection) {
|
||||||
|
let template;
|
||||||
|
|
||||||
// generate summary and footer
|
if (selection === this.total) {
|
||||||
this.summary.$el = this.$formGroup.find('.selection-summary');
|
template = 'all';
|
||||||
this.footer.$el = this.footer.getEl(this);
|
} else if (selection > 0) {
|
||||||
this.footer.update(this);
|
template = 'some';
|
||||||
|
} else {
|
||||||
|
template = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// create summary from component pieces and match text to current selection
|
this.$text.html(this.templates[template](selection, this.total, this.fieldLabel));
|
||||||
this.summary.addContent(this.legendText, this.fieldLabel);
|
};
|
||||||
this.summary.update(this.getSelection(), this.total, this.fieldLabel);
|
Summary.prototype.bindEvents = function () {
|
||||||
this.$fieldset.before(this.summary.$el);
|
// take summary out of tab order when focus moves
|
||||||
|
this.$el.on('blur', (e) => $(this).attr('tabindex', '-1'));
|
||||||
// add custom classes
|
|
||||||
this.$formGroup.addClass('selection-wrapper');
|
|
||||||
this.$fieldset.addClass('selection-content');
|
|
||||||
|
|
||||||
// hide checkboxes
|
|
||||||
this.$fieldset.hide();
|
|
||||||
|
|
||||||
this.bindEvents();
|
|
||||||
};
|
|
||||||
this.getSelection = function() { return this.$checkboxes.filter(':checked').length; };
|
|
||||||
this.addHeadingHideLegend = function() {
|
|
||||||
const headingLevel = this.$formGroup.data('heading-level') || '2';
|
|
||||||
|
|
||||||
this.$heading = $(`<h${headingLevel} class="heading-small">${this.legendText}</h${headingLevel}>`);
|
|
||||||
this.$fieldset.before(this.$heading);
|
|
||||||
|
|
||||||
this.$fieldset.find('legend').addClass('visuallyhidden');
|
|
||||||
};
|
|
||||||
this.summary = {
|
|
||||||
templates: {
|
|
||||||
all: (selection, total, field) => `All ${field}s`,
|
|
||||||
some: (selection, total, field) => `${selection} of ${total} ${field}s`,
|
|
||||||
none: (selection, total, field) => ({
|
|
||||||
"folder": "No folders (only templates outside a folder)",
|
|
||||||
"team member": "No team members (only you)"
|
|
||||||
}[field] || `No ${field}s`)
|
|
||||||
},
|
|
||||||
addContent: function(legendText, fieldLabel) {
|
|
||||||
this.$text = $(`<p class="selection-summary__text" />`);
|
|
||||||
|
|
||||||
if (fieldLabel === 'folder') { this.$text.addClass('selection-summary__text--folders'); }
|
|
||||||
|
|
||||||
this.$el.append(this.$text);
|
|
||||||
},
|
|
||||||
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.footer = {
|
|
||||||
buttonContent: {
|
|
||||||
change: (fieldLabel) => `Choose ${fieldLabel}s`,
|
|
||||||
done: (fieldLabel) => `Done<span class="visuallyhidden"> choosing ${fieldLabel}s</span>`
|
|
||||||
},
|
|
||||||
getEl: function (module) {
|
|
||||||
const buttonState = module.expanded ? 'done' : 'change';
|
|
||||||
const buttonContent = this.buttonContent[buttonState](module.fieldLabel);
|
|
||||||
|
|
||||||
return $(`<div class="selection-footer">
|
|
||||||
<button
|
|
||||||
class="button button-secondary"
|
|
||||||
aria-expanded="${module.expanded ? 'true' : 'false'}"
|
|
||||||
aria-controls="${module.$fieldset.attr('id')}">
|
|
||||||
${buttonContent}
|
|
||||||
</button>
|
|
||||||
</div>`);
|
|
||||||
},
|
|
||||||
update: function (module) {
|
|
||||||
this.$el.remove();
|
|
||||||
this.$el = this.getEl(module);
|
|
||||||
|
|
||||||
module.$formGroup.append(this.$el);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.expand = function(e) {
|
|
||||||
if (e !== undefined) { e.preventDefault(); }
|
|
||||||
|
|
||||||
if (!this.expanded) {
|
|
||||||
this.$fieldset.show();
|
|
||||||
this.expanded = true;
|
|
||||||
this.footer.update(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.expanded = false;
|
|
||||||
this.footer.update(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// shift focus whether expanded or not
|
|
||||||
_focusTextElement(this.summary.$text);
|
|
||||||
};
|
|
||||||
this.handleClick = function(e) {
|
|
||||||
if (this.expanded) {
|
|
||||||
this.collapse(e);
|
|
||||||
} else {
|
|
||||||
this.expand(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.handleSelection = function(e) {
|
|
||||||
this.summary.update(this.getSelection(), this.total, this.fieldLabel);
|
|
||||||
};
|
|
||||||
this.bindEvents = function() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
this.$formGroup.on('click', '.button', this.handleClick.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'));
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function Footer (module) {
|
||||||
|
this.module = module;
|
||||||
|
this.fieldLabel = module.fieldLabel;
|
||||||
|
this.fieldsetId = module.$fieldset.attr('id');
|
||||||
|
this.$el = this.getEl(this.module.expanded);
|
||||||
|
this.module.$formGroup.append(this.$el);
|
||||||
|
}
|
||||||
|
Footer.prototype.buttonContent = {
|
||||||
|
change: (fieldLabel) => `Choose ${fieldLabel}s`,
|
||||||
|
done: (fieldLabel) => `Done<span class="visuallyhidden"> choosing ${fieldLabel}s</span>`
|
||||||
|
};
|
||||||
|
Footer.prototype.getEl = function (expanded) {
|
||||||
|
const buttonState = expanded ? 'done' : 'change';
|
||||||
|
const buttonContent = this.buttonContent[buttonState](this.fieldLabel);
|
||||||
|
|
||||||
|
return $(`<div class="selection-footer">
|
||||||
|
<button
|
||||||
|
class="button button-secondary"
|
||||||
|
aria-expanded="${expanded ? 'true' : 'false'}"
|
||||||
|
aria-controls="${this.fieldsetId}">
|
||||||
|
${buttonContent}
|
||||||
|
</button>
|
||||||
|
</div>`);
|
||||||
|
};
|
||||||
|
Footer.prototype.update = function (expanded) {
|
||||||
|
this.$el.remove();
|
||||||
|
this.$el = this.getEl(expanded);
|
||||||
|
|
||||||
|
this.module.$formGroup.append(this.$el);
|
||||||
|
};
|
||||||
|
|
||||||
|
function CollapsibleCheckboxes () {}
|
||||||
|
CollapsibleCheckboxes.prototype._focusTextElement = ($el) => {
|
||||||
|
$el
|
||||||
|
.attr('tabindex', '-1')
|
||||||
|
.focus();
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.start = function(component) {
|
||||||
|
this.$formGroup = $(component);
|
||||||
|
this.$fieldset = this.$formGroup.find('fieldset');
|
||||||
|
this.$checkboxes = this.$fieldset.find('input[type=checkbox]');
|
||||||
|
this.fieldLabel = this.$formGroup.data('fieldLabel');
|
||||||
|
this.total = this.$checkboxes.length;
|
||||||
|
this.legendText = this.$fieldset.find('legend').text().trim();
|
||||||
|
this.expanded = false;
|
||||||
|
|
||||||
|
this.addHeadingHideLegend();
|
||||||
|
|
||||||
|
// generate summary and footer
|
||||||
|
this.footer = new Footer(this);
|
||||||
|
this.summary = new Summary(this);
|
||||||
|
|
||||||
|
this.$fieldset.before(this.summary.$el);
|
||||||
|
|
||||||
|
// add custom classes
|
||||||
|
this.$formGroup.addClass('selection-wrapper');
|
||||||
|
this.$fieldset.addClass('selection-content');
|
||||||
|
|
||||||
|
// hide checkboxes
|
||||||
|
this.$fieldset.hide();
|
||||||
|
|
||||||
|
this.bindEvents();
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.getSelection = function() { return this.$checkboxes.filter(':checked').length; };
|
||||||
|
CollapsibleCheckboxes.prototype.addHeadingHideLegend = function() {
|
||||||
|
const headingLevel = this.$formGroup.data('heading-level') || '2';
|
||||||
|
|
||||||
|
this.$heading = $(`<h${headingLevel} class="heading-small">${this.legendText}</h${headingLevel}>`);
|
||||||
|
this.$fieldset.before(this.$heading);
|
||||||
|
|
||||||
|
this.$fieldset.find('legend').addClass('visuallyhidden');
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.expand = function(e) {
|
||||||
|
if (e !== undefined) { e.preventDefault(); }
|
||||||
|
|
||||||
|
if (!this.expanded) {
|
||||||
|
this.$fieldset.show();
|
||||||
|
this.expanded = true;
|
||||||
|
this.summary.update(this.getSelection());
|
||||||
|
this.footer.update(this.expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift focus whether expanded or not
|
||||||
|
this._focusTextElement(this.$fieldset);
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.collapse = function(e) {
|
||||||
|
if (e !== undefined) { e.preventDefault(); }
|
||||||
|
|
||||||
|
if (this.expanded) {
|
||||||
|
this.$fieldset.hide();
|
||||||
|
this.expanded = false;
|
||||||
|
this.summary.update(this.getSelection());
|
||||||
|
this.footer.update(this.expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift focus whether expanded or not
|
||||||
|
this._focusTextElement(this.summary.$text);
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.handleClick = function(e) {
|
||||||
|
if (this.expanded) {
|
||||||
|
this.collapse(e);
|
||||||
|
} else {
|
||||||
|
this.expand(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.handleSelection = function(e) {
|
||||||
|
this.summary.update(this.getSelection(), this.total, this.fieldLabel);
|
||||||
|
};
|
||||||
|
CollapsibleCheckboxes.prototype.bindEvents = function() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.$formGroup.on('click', '.button', this.handleClick.bind(this));
|
||||||
|
this.$checkboxes.on('click', this.handleSelection.bind(this));
|
||||||
|
|
||||||
|
this.summary.bindEvents(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Modules.CollapsibleCheckboxes = CollapsibleCheckboxes;
|
||||||
|
|
||||||
}(window.GOVUK.Modules));
|
}(window.GOVUK.Modules));
|
||||||
|
|||||||
Reference in New Issue
Block a user