UNPKG

digitalmarketplace-frontend

Version:

Digital Marketplace Frontend contains assets and components used in Digital Marketplace projects

177 lines (173 loc) 8.31 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DigitalMarketplaceFrontend = {})); })(this, (function (exports) { 'use strict'; class OptionSelect { static moduleName = 'dm-option-select'; $optionSelect; $options; $optionsContainer; $optionList; $allCheckboxes; checkedCheckboxes; constructor($module) { this.$optionSelect = $module; this.$options = this.$optionSelect.find('input[type="checkbox"]'); this.$optionsContainer = this.$optionSelect.find('.js-options-container'); this.$optionList = this.$optionsContainer.find('.js-auto-height-inner'); this.$allCheckboxes = this.$optionsContainer.find('.govuk-checkboxes__item'); this.checkedCheckboxes = []; } init() { // Attach listener to update checked count this.$optionSelect.on('change', (event) => { const changedEl = event.target; if (changedEl.tagName === 'INPUT' && changedEl.getAttribute('type') === 'checkbox') { this.updateCheckedCount(); } }); // Replace div.container-head with a button this.replaceHeadingSpanWithButton(); // Add js-collapsible class to parent for CSS this.$optionSelect.addClass('js-collapsible'); // Add open/close listeners this.$optionSelect.find('.js-container-button').on('click', (event) => { this.toggleOptionSelect(event); }); if (this.$optionSelect.data().closedOnLoad) { this.close(); } else { this.setupHeight(); } const checkedString = this.checkedString(); if (checkedString) { this.attachCheckedCounter(checkedString); } } getAllCheckedCheckboxes() { this.checkedCheckboxes = []; this.$allCheckboxes.each((_index, checkbox) => { if ($(checkbox).find('input[type=checkbox]').is(':checked')) { this.checkedCheckboxes.push(checkbox); } }); } replaceHeadingSpanWithButton() { /* Replace the span within the heading with a button element. This is based on feedback from Léonie Watson. * The button has all of the accessibility hooks that are used by screen readers and etc. * We do this in the JavaScript because if the JavaScript is not active then the button shouldn't * be there as there is no JS to handle the click event. */ const $containerHead = this.$optionSelect.find('.js-container-button'); const jsContainerHeadHTML = $containerHead.html(); // Create button and replace the preexisting html with the button. const $button = document.createElement('button'); $button.classList.add('js-container-button', 'dm-option-select__title', 'dm-option-select__button'); // Add type button to override default type submit when this component is used within a form $button.setAttribute('type', 'button'); $button.setAttribute('aria-expanded', 'true'); $button.setAttribute('id', $containerHead.attr('id')); $button.setAttribute('aria-controls', this.$optionsContainer.attr('id')); $button.innerHTML = jsContainerHeadHTML; $containerHead.after($button.outerHTML); $containerHead.remove(); } attachCheckedCounter(checkedString) { this.$optionSelect.find('.js-container-button').after(`<div class="dm-option-select__selected-counter js-selected-counter">${checkedString}</div>`); } updateCheckedCount() { const checkedString = this.checkedString(); const $checkedStringElement = this.$optionSelect.find('.js-selected-counter'); if (checkedString) { if ($checkedStringElement.length) { $checkedStringElement.text(checkedString); } else { this.attachCheckedCounter(checkedString); } } else { $checkedStringElement.remove(); } } checkedString() { this.getAllCheckedCheckboxes(); const count = this.checkedCheckboxes.length; let checkedString = ''; if (count > 0) { checkedString = count + ' selected'; } return checkedString; } toggleOptionSelect(event) { if (this.isClosed()) { this.open(); } else { this.close(); } event.preventDefault(); } open() { if (this.isClosed()) { this.$optionSelect.find('.js-container-button').attr('aria-expanded', 'true'); this.$optionSelect.removeClass('js-closed'); this.$optionSelect.addClass('js-opened'); if (!this.$optionsContainer.css('height')) { this.setupHeight(); } } } close() { this.$optionSelect.removeClass('js-opened'); this.$optionSelect.addClass('js-closed'); this.$optionSelect.find('.js-container-button').attr('aria-expanded', 'false'); } isClosed() { return this.$optionSelect.hasClass('js-closed'); } setContainerHeight(height) { this.$optionsContainer.css('height', height + 'px'); } isCheckboxVisible($checkbox) { const initialOptionContainerHeight = this.$optionsContainer.get(0)?.clientHeight; const optionListOffsetTop = this.$optionList.get(0).getBoundingClientRect().top + document.body.scrollTop; const distanceFromTopOfContainer = ($checkbox.get(0).getBoundingClientRect().top + document.body.scrollTop) - optionListOffsetTop; return distanceFromTopOfContainer < initialOptionContainerHeight; } getVisibleCheckboxes() { const visibleCheckboxes = this.$options.get().filter((checkbox) => this.isCheckboxVisible($(checkbox))); // add an extra checkbox, if the label of the first is too long it collapses onto itself if (visibleCheckboxes.length < this.$options.length) { visibleCheckboxes.push(this.$options.get(visibleCheckboxes.length)); } return visibleCheckboxes; } setupHeight() { const optionsContainer = this.$optionsContainer.get(0); const optionList = this.$optionList.get(0); let initialOptionContainerHeight = optionsContainer.clientHeight; let height = optionList.offsetHeight; // check whether this is hidden by progressive disclosure, // because height calculations won't work if (optionsContainer.offsetParent === null) { initialOptionContainerHeight = 200; height = 200; } // Resize if the list is only slightly bigger than its container if (height < initialOptionContainerHeight + 50) { this.setContainerHeight(height + 1); return; } // Resize to cut last item cleanly in half const visibleCheckboxes = this.getVisibleCheckboxes(); const lastVisibleCheckbox = visibleCheckboxes[visibleCheckboxes.length - 1]; const position = (lastVisibleCheckbox?.parentNode).offsetTop; // parent element is relative this.setContainerHeight(position + (parseFloat(window.getComputedStyle(lastVisibleCheckbox, null).height.replace('px', '')) / 1.5)); } } exports.OptionSelect = OptionSelect; })); //# sourceMappingURL=option-select.bundle.js.map