UNPKG

digitalmarketplace-govuk-frontend

Version:

Digital Marketplace GOV.UK Frontend contains the code you need to start building a user interface for Digital Marketplace.

178 lines (151 loc) 6.86 kB
function OptionSelect ($module) { this.$optionSelect = $module this.$options = this.$optionSelect.querySelectorAll("input[type='checkbox']") this.$optionsContainer = this.$optionSelect.querySelector('.js-options-container') this.$optionList = this.$optionsContainer.querySelector('.js-auto-height-inner') this.$allCheckboxes = this.$optionsContainer.querySelectorAll('.govuk-checkboxes__item') this.checkedCheckboxes = [] } OptionSelect.prototype.init = function () { // Attach listener to update checked count this.$optionSelect.addEventListener('change', function (event) { var $changedEl = event.target if ($changedEl.tagName === 'INPUT' && $changedEl.type === 'checkbox') { this.updateCheckedCount() } }.bind(this)) // Replace div.container-head with a button this.replaceHeadingSpanWithButton() // Add js-collapsible class to parent for CSS this.$optionSelect.classList.add('js-collapsible') // Add open/close listeners this.$optionSelect.querySelector('.js-container-button').addEventListener('click', this.toggleOptionSelect.bind(this)) if (this.$optionSelect.dataset.closedOnLoad === 'true') { this.close() } else { this.setupHeight() } var checkedString = this.checkedString() if (checkedString) { this.attachCheckedCounter(checkedString) } } OptionSelect.prototype.getAllCheckedCheckboxes = function getAllCheckedCheckboxes () { this.checkedCheckboxes = [] var that = this this.$allCheckboxes.forEach(function (i) { if (i.querySelector('input[type=checkbox]').checked) { that.checkedCheckboxes.push(i) } }, this) } OptionSelect.prototype.replaceHeadingSpanWithButton = function 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. */ var $containerHead = this.$optionSelect.querySelector('.js-container-button') var jsContainerHeadHTML = $containerHead.innerHTML // Create button and replace the preexisting html with the button. var $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.id) $button.setAttribute('aria-controls', this.$optionsContainer.id) $button.innerHTML = jsContainerHeadHTML $containerHead.insertAdjacentHTML('afterend', $button.outerHTML) $containerHead.parentNode.removeChild($containerHead) } OptionSelect.prototype.attachCheckedCounter = function attachCheckedCounter (checkedString) { this.$optionSelect.querySelector('.js-container-button') .insertAdjacentHTML('afterend', '<div class="dm-option-select__selected-counter js-selected-counter">' + checkedString + '</div>') } OptionSelect.prototype.updateCheckedCount = function updateCheckedCount () { var checkedString = this.checkedString() var checkedStringElement = this.$optionSelect.querySelector('.js-selected-counter') if (checkedString) { if (checkedStringElement) { checkedStringElement.textContent = checkedString } else { this.attachCheckedCounter(checkedString) } } else { checkedStringElement.parentNode.removeChild(checkedStringElement) } } OptionSelect.prototype.checkedString = function checkedString () { this.getAllCheckedCheckboxes() var count = this.checkedCheckboxes.length var checkedString = false if (count > 0) { checkedString = count + ' selected' } return checkedString } OptionSelect.prototype.toggleOptionSelect = function toggleOptionSelect (e) { if (this.isClosed()) { this.open() } else { this.close() } e.preventDefault() } OptionSelect.prototype.open = function open () { if (this.isClosed()) { this.$optionSelect.querySelector('.js-container-button').setAttribute('aria-expanded', true) this.$optionSelect.classList.remove('js-closed') this.$optionSelect.classList.add('js-opened') if (!this.$optionsContainer.style.height) { this.setupHeight() } } } OptionSelect.prototype.close = function close () { this.$optionSelect.classList.remove('js-opened') this.$optionSelect.classList.add('js-closed') this.$optionSelect.querySelector('.js-container-button').setAttribute('aria-expanded', false) } OptionSelect.prototype.isClosed = function isClosed () { return this.$optionSelect.classList.contains('js-closed') } OptionSelect.prototype.setContainerHeight = function setContainerHeight (height) { this.$optionsContainer.style.height = height + 'px' } OptionSelect.prototype.isCheckboxVisible = function isCheckboxVisible ($checkbox) { var initialOptionContainerHeight = this.$optionsContainer.clientHeight var optionListOffsetTop = this.$optionList.getBoundingClientRect().top + document.body.scrollTop var distanceFromTopOfContainer = ($checkbox.getBoundingClientRect().top + document.body.scrollTop) - optionListOffsetTop return distanceFromTopOfContainer < initialOptionContainerHeight } OptionSelect.prototype.getVisibleCheckboxes = function getVisibleCheckboxes () { var visibleCheckboxes = ([].slice.call(this.$options)).filter(this.isCheckboxVisible.bind(this)) // add an extra checkbox, if the label of the first is too long it collapses onto itself if (visibleCheckboxes.length < this.$options) { visibleCheckboxes.push(this.$options[visibleCheckboxes.length]) } return visibleCheckboxes } OptionSelect.prototype.setupHeight = function setupHeight () { var initialOptionContainerHeight = this.$optionsContainer.clientHeight var height = this.$optionList.offsetHeight // check whether this is hidden by progressive disclosure, // because height calculations won't work if (this.$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 var visibleCheckboxes = this.getVisibleCheckboxes() var lastVisibleCheckbox = visibleCheckboxes[visibleCheckboxes.length - 1] var position = lastVisibleCheckbox.parentNode.offsetTop // parent element is relative this.setContainerHeight(position + (parseFloat(window.getComputedStyle(lastVisibleCheckbox, null).height.replace('px', '')) / 1.5)) } export default OptionSelect