Skip to content

Commit

Permalink
Merge pull request #1620 from alphagov/replace-jquery-in-checkboxes-js
Browse files Browse the repository at this point in the history
Replace jQuery in checkboxes.js
  • Loading branch information
alex-ju authored Aug 18, 2020
2 parents fd81c74 + 48d408c commit 2fca57e
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 80 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
## Unreleased

* Add public/frontend layout component ([PR #1265](https://github.com/alphagov/govuk_publishing_components/pull/1265))
* Replace jQuery in checkboxes.js ([PR #1620](https://github.com/alphagov/govuk_publishing_components/pull/1620))

## 21.60.3

Expand Down
Original file line number Diff line number Diff line change
@@ -1,94 +1,123 @@
/* eslint-env jquery */
// = require govuk/vendor/polyfills/Element/prototype/closest.js
// = require govuk/components/checkboxes/checkboxes.js
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {}
window.GOVUK.Modules.Checkboxes = window.GOVUKFrontend;

(function (Modules) {
'use strict'

Modules.GovukCheckboxes = function () {
this.start = function (scope) {
var _this = this
this.applyAriaControlsAttributes(scope)

$(scope).on('change', '[data-nested=true] input[type=checkbox]', function (e) {
var checkbox = e.target
var isNested = $(checkbox).closest('.govuk-checkboxes--nested')
var hasNested = $('.govuk-checkboxes--nested[data-parent=' + checkbox.id + ']')

if (hasNested.length) {
_this.toggleNestedCheckboxes(hasNested, checkbox)
} else if (isNested.length) {
_this.toggleParentCheckbox(isNested, checkbox)
}
})

$(scope).on('change', 'input[type=checkbox]', function (e) {
if (window.GOVUK.analytics && window.GOVUK.analytics.trackEvent) {
// where checkboxes are manipulated externally in finders, suppressAnalytics
// is passed to prevent duplicate GA events
if (typeof e.suppressAnalytics === 'undefined' || e.suppressAnalytics !== true) {
var $checkbox = $(e.target)
var category = $checkbox.data('track-category')
if (typeof category !== 'undefined') {
var isChecked = $checkbox.is(':checked')
var uncheckTrackCategory = $checkbox.data('uncheck-track-category')
if (!isChecked && typeof uncheckTrackCategory !== 'undefined') {
category = uncheckTrackCategory
}
var action = $checkbox.data('track-action')
var options = $checkbox.data('track-options')
if (typeof options !== 'object' || options === null) {
options = {}
}
options.value = $checkbox.data('track-value')
options.label = $checkbox.data('track-label')
window.GOVUK.analytics.trackEvent(category, action, options)
}
function GovukCheckboxes () { }

GovukCheckboxes.prototype.start = function ($module) {
this.$module = $module[0]
this.$checkboxes = this.$module.querySelectorAll('input[type=checkbox]')
this.$nestedCheckboxes = this.$module.querySelectorAll('[data-nested=true] input[type=checkbox]')
this.$exclusiveCheckboxes = this.$module.querySelectorAll('[data-exclusive=true] input[type=checkbox]')

this.applyAriaControlsAttributes(this.$module)

for (var i = 0; i < this.$checkboxes.length; i++) {
this.$checkboxes[i].addEventListener('change', this.handleCheckboxChange)
}

for (i = 0; i < this.$nestedCheckboxes.length; i++) {
this.$nestedCheckboxes[i].addEventListener('change', this.handleNestedCheckboxChange.bind(this))
}

for (i = 0; i < this.$exclusiveCheckboxes.length; i++) {
this.$exclusiveCheckboxes[i].addEventListener('change', this.handleExclusiveCheckboxChange)
}
}

GovukCheckboxes.prototype.handleCheckboxChange = function (event) {
if (window.GOVUK.analytics && window.GOVUK.analytics.trackEvent) {
// Where checkboxes are manipulated externally in finders, `suppressAnalytics`
// is passed to prevent duplicate GA events.
if (!event.detail || (event.detail && event.detail.suppressAnalytics !== true)) {
var $checkbox = event.target
var category = $checkbox.getAttribute('data-track-category')
if (category) {
var uncheckTrackCategory = $checkbox.getAttribute('data-uncheck-track-category')
if (!$checkbox.checked && uncheckTrackCategory) {
category = uncheckTrackCategory
}
var action = $checkbox.getAttribute('data-track-action')
var options = $checkbox.getAttribute('data-track-options')
if (options) {
options = JSON.parse(options)
} else {
options = {}
}
options.value = $checkbox.getAttribute('data-track-value')
options.label = $checkbox.getAttribute('data-track-label')
window.GOVUK.analytics.trackEvent(category, action, options)
}
})

$(scope).on('change', '[data-exclusive=true] input[type=checkbox]', function (e) {
var currentCheckbox = e.target
var checkboxes = currentCheckbox.closest('.govuk-checkboxes')
var exclusiveOption = $(checkboxes).find('input[type=checkbox][data-exclusive]')
var nonExclusiveOptions = $(checkboxes).find('input[type=checkbox]:not([data-exclusive])')

if (currentCheckbox.dataset.exclusive === 'true' && currentCheckbox.checked === true) {
nonExclusiveOptions.each(function () {
$(this).prop('checked', false)
})
} else if (currentCheckbox.dataset.exclusive !== 'true' && currentCheckbox.checked === true) {
exclusiveOption.prop('checked', false)
}
})
}
}
}

this.toggleNestedCheckboxes = function (scope, checkbox) {
if (checkbox.checked) {
scope.find('input[type=checkbox]').prop('checked', true)
} else {
scope.find('input[type=checkbox]').prop('checked', false)
GovukCheckboxes.prototype.handleNestedCheckboxChange = function (event) {
var $checkbox = event.target
var $isNested = $checkbox.closest('.govuk-checkboxes--nested')
var $hasNested = this.$module.querySelector('.govuk-checkboxes--nested[data-parent=' + $checkbox.id + ']')

if ($hasNested) {
this.toggleNestedCheckboxes($hasNested, $checkbox)
} else if ($isNested) {
this.toggleParentCheckbox($isNested, $checkbox)
}
}

GovukCheckboxes.prototype.toggleNestedCheckboxes = function ($scope, $checkbox) {
var $nestedCheckboxes = $scope.querySelectorAll('input[type=checkbox]')
if ($checkbox.checked) {
for (var i = 0; i < $nestedCheckboxes.length; i++) {
$nestedCheckboxes[i].checked = true
}
} else {
for (i = 0; i < $nestedCheckboxes.length; i++) {
$nestedCheckboxes[i].checked = false
}
}
}

GovukCheckboxes.prototype.toggleParentCheckbox = function ($scope, $checkbox) {
var $inputs = $scope.querySelectorAll('input')
var $checkedInputs = $scope.querySelectorAll('input:checked')
var parentId = $scope.getAttribute('data-parent')
var $parent = document.getElementById(parentId)

if ($checkbox.checked && $inputs.length === $checkedInputs.length) {
$parent.checked = true
} else {
$parent.checked = false
}
}

this.toggleParentCheckbox = function (scope, checkbox) {
var siblings = $(checkbox).closest('.govuk-checkboxes__item').siblings()
var parentId = scope.data('parent')
GovukCheckboxes.prototype.handleExclusiveCheckboxChange = function (event) {
var $currentCheckbox = event.target
var $checkboxes = $currentCheckbox.closest('.govuk-checkboxes')
var $exclusiveOption = $checkboxes.querySelector('input[type=checkbox][data-exclusive]')
var $nonExclusiveOptions = $checkboxes.querySelectorAll('input[type=checkbox]:not([data-exclusive])')

if (checkbox.checked && siblings.length === siblings.find(':checked').length) {
$('#' + parentId).prop('checked', true)
} else {
$('#' + parentId).prop('checked', false)
if ($currentCheckbox.dataset.exclusive === 'true' && $currentCheckbox.checked === true) {
for (var i = 0; i < $nonExclusiveOptions.length; i++) {
$nonExclusiveOptions[i].checked = false
}
} else if ($currentCheckbox.dataset.exclusive !== 'true' && $currentCheckbox.checked === true) {
if ($exclusiveOption) {
$exclusiveOption.checked = false
}
}
}

GovukCheckboxes.prototype.applyAriaControlsAttributes = function ($scope) {
var $inputs = $scope.querySelectorAll('[data-controls]')

this.applyAriaControlsAttributes = function (scope) {
$(scope).find('[data-controls]').each(function () {
$(this).attr('aria-controls', $(this).attr('data-controls'))
})
for (var i = 0; i < $inputs.length; i++) {
$inputs[i].setAttribute('aria-controls', $inputs[i].getAttribute('data-controls'))
}
}

Modules.GovukCheckboxes = GovukCheckboxes
})(window.GOVUK.Modules)
15 changes: 8 additions & 7 deletions spec/javascripts/components/checkboxes-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ describe('Checkboxes component', function () {
$checkboxesWrapper = $('.gem-c-checkboxes')
$exclusiveOption = $checkboxesWrapper.find('input[type=checkbox][data-exclusive]')
$nonExclusiveOptions = $checkboxesWrapper.find('input[type=checkbox]:not([data-exclusive])')
expectedRedOptions = { label: 'red', value: 1, dimension28: 'wubbalubbadubdub', dimension29: 'Pickle Rick' }
expectedBlueOptions = { label: 'blue', value: 2, dimension28: 'Get schwifty', dimension29: 'Squanch' }
expectedRedOptions = { label: 'red', value: '1', dimension28: 'wubbalubbadubdub', dimension29: 'Pickle Rick' }
expectedBlueOptions = { label: 'blue', value: '2', dimension28: 'Get schwifty', dimension29: 'Squanch' }

GOVUK.analytics = {
trackEvent: function () {}
Expand Down Expand Up @@ -144,21 +144,22 @@ describe('Checkboxes component', function () {
describe('controlling Google analytics track event when a checkbox is changed', function () {
it('fires a Google analytics event if suppressAnalytics not passed to the change event', function () {
var $checkbox = $checkboxesWrapper.find(":input[value='blue']")
$checkbox.trigger('change')
var fakeOnChangeEvent = new window.CustomEvent('change')
$checkbox[0].dispatchEvent(fakeOnChangeEvent)
expect(GOVUK.analytics.trackEvent).toHaveBeenCalled()
})

it('fires a Google analytics event if suppressAnalytics is set to false and passed to the change event', function () {
var $checkbox = $checkboxesWrapper.find(":input[value='blue']")
var fakeOnChangeEvent = { type: 'change', suppressAnalytics: false }
$checkbox.trigger(fakeOnChangeEvent)
var fakeOnChangeEvent = new window.CustomEvent('change', { detail: { suppressAnalytics: false } })
$checkbox[0].dispatchEvent(fakeOnChangeEvent)
expect(GOVUK.analytics.trackEvent).toHaveBeenCalled()
})

it('does not fire a Google analytics event if suppressAnalytics is passed to the change event', function () {
var $checkbox = $checkboxesWrapper.find(":input[value='blue']")
var fakeOnChangeEvent = { type: 'change', suppressAnalytics: true }
$checkbox.trigger(fakeOnChangeEvent)
var fakeOnChangeEvent = new window.CustomEvent('change', { detail: { suppressAnalytics: true } })
$checkbox[0].dispatchEvent(fakeOnChangeEvent)
expect(GOVUK.analytics.trackEvent).not.toHaveBeenCalled()
})
})
Expand Down

0 comments on commit 2fca57e

Please sign in to comment.