diff --git a/CHANGELOG.md b/CHANGELOG.md index 872baab325..00af4b45cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## Unreleased * Alter use of pseudo-underline mixin to allow for different button sizes ([#2501](https://github.com/alphagov/govuk_publishing_components/pull/2501)) +* Re-work explicit-cross-domain-links.js ([PR #2502](https://github.com/alphagov/govuk_publishing_components/pull/2502)) ## 27.16.0 diff --git a/app/assets/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.js b/app/assets/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.js index aed08fbf50..bea6137eeb 100644 --- a/app/assets/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.js +++ b/app/assets/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.js @@ -6,33 +6,57 @@ GOVUK.Modules.ExplicitCrossDomainLinks = function () { this.start = function ($module) { - var element = $module[0] + this.element = $module[0] + this.attribute = 'href' + this.attributeValue = this.element.getAttribute(this.attribute) + this.eventType = 'click' + if (!this.attributeValue) { + this.attribute = 'action' + this.attributeValue = this.element.getAttribute(this.attribute) + this.eventType = 'submit' + } - var cookieBannerEngaged = GOVUK.cookie('cookies_preferences_set') + this.handleEvent = this.handleEvent.bind(this) + this.handleCookiesAccepted = this.handleCookiesAccepted.bind(this) + // Listens for the 'submit' event if the element is a form, and the 'click' event if it is a link + this.element.addEventListener(this.eventType, this.handleEvent) + } - // If not engaged, append only ?cookie_consent=not-engaged - // If engaged and rejected, append only ?cookie_consent=reject - // If engaged and accepted usage, append ?_ga=clientid if available and cookie_consent=accept + this.handleEvent = function (e) { + // prevent default: we want the link href and/or form action to be decorated before we navigate away + e.preventDefault() + var cookieBannerEngaged = GOVUK.cookie('cookies_preferences_set') + var cookieConsent = GOVUK.getConsentCookie() if (cookieBannerEngaged !== 'true') { - this.decorate(element, 'cookie_consent=not-engaged') - this.start = this.start.bind(this, $module) - - // if the user has not engaged with the cookie banner yet, listen for the cookie consent accept/reject events - // re-start the module if cookies are accepted or rejected on the current page – setting cookie preferences does not reload the page - window.addEventListener('cookie-consent', this.start) - window.addEventListener('cookie-reject', this.start) - return + // If not engaged, append only ?cookie_consent=not-engaged + this.decorate(this.element, 'cookie_consent=not-engaged', this.attribute) + } else if (cookieConsent && cookieConsent.usage === true) { + this.handleCookiesAccepted() + } else { + this.decorate(this.element, 'cookie_consent=reject', this.attribute) } - var cookieConsent = GOVUK.getConsentCookie() - if (cookieConsent && cookieConsent.usage === false) { - this.decorate(element, 'cookie_consent=reject') - return + + // remove the event listener to avoid an infinite loop + this.element.removeEventListener(this.eventType, this.handleEvent) + + // if the element is a form, submit it. If it is a link, click it + if (this.eventType === 'submit') { + this.element.submit() + } else { + this.element.click() } + } - this.decorate(element, 'cookie_consent=accept') + this.handleCookiesAccepted = function () { + // If the cookie banner was engaged and usage cookie accepted, append ?_ga=clientid if available and cookie_consent=accept + var element = this.element + var attribute = this.attribute + this.decorate(element, 'cookie_consent=accept', attribute) - if (!global.ga) { return } + if (!global.ga) { + return + } global.ga(function () { var trackers = global.ga.getAll() @@ -40,44 +64,21 @@ if (!trackers.length) { return } var linker = new global.gaplugins.Linker(trackers[0]) + var attrValue = element.getAttribute(attribute) - var attrAction = element.getAttribute('action') - if (attrAction) { - element.setAttribute('action', linker.decorate(attrAction)) - } - - var attrHref = element.getAttribute('href') - if (attrHref) { - element.href = linker.decorate(attrHref) - } + element.setAttribute(attribute, linker.decorate(attrValue)) }) } - this.decorate = function (element, param) { - var attribute = 'href' + this.decorate = function (element, param, attribute) { var attributeValue = element.getAttribute(attribute) - var cookieConsentParameterPattern = /cookie_consent=[^&]*/ - var paramIsCookieConsent = param.match(cookieConsentParameterPattern) - - if (!attributeValue) { - attribute = 'action' - attributeValue = element.getAttribute(attribute) - } if (!attributeValue) { return } - var attributeHasCookieConsent = attributeValue.match(cookieConsentParameterPattern) - - if (attributeHasCookieConsent && paramIsCookieConsent) { - // if the decorate function has received a cookie_consent parameter, but the target element already has a cookie_consent parameter, replace the existing parameter with the new value - attributeValue = attributeValue.replace(cookieConsentParameterPattern, param) + if (attributeValue.includes('?')) { + attributeValue += '&' + param } else { - // otherwise, simply append the parameter to the target element href query string - if (attributeValue.includes('?')) { - attributeValue += '&' + param - } else { - attributeValue += '?' + param - } + attributeValue += '?' + param } element.setAttribute(attribute, attributeValue) diff --git a/app/assets/javascripts/govuk_publishing_components/components/cookie-banner.js b/app/assets/javascripts/govuk_publishing_components/components/cookie-banner.js index a8697eec4c..d3a833e723 100644 --- a/app/assets/javascripts/govuk_publishing_components/components/cookie-banner.js +++ b/app/assets/javascripts/govuk_publishing_components/components/cookie-banner.js @@ -97,7 +97,6 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; this.$module.cookieBannerConfirmationMessage.focus() window.GOVUK.cookie('cookies_preferences_set', 'true', { days: 365 }) window.GOVUK.setDefaultConsentCookie() - window.GOVUK.triggerEvent(window, 'cookie-reject') } CookieBanner.prototype.showConfirmationMessage = function () { diff --git a/docs/javascript-modules.md b/docs/javascript-modules.md index 34ea5f6085..a36febbc30 100644 --- a/docs/javascript-modules.md +++ b/docs/javascript-modules.md @@ -69,8 +69,6 @@ This functionality runs like this: - if cookies have been consented, the module calls the rest of its code and carries on as normal - if cookies have not been consented, the listener is created and calls the rest of the module when the `cookie-consent` event is fired by the cookie banner -If a module has functionality which is dependent on rejecting cookies, that module should listen for the `cookie-reject` event. This event is fired by the cookie banner when the user rejects cookies. - ### Module structure A module must add its constructor to `GOVUK.Modules` and it must have an `init` method. The simplest module looks like: diff --git a/spec/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.spec.js b/spec/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.spec.js index 4afd22b15d..d7116b4c8e 100644 --- a/spec/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.spec.js +++ b/spec/javascripts/govuk_publishing_components/analytics/explicit-cross-domain-links.spec.js @@ -42,28 +42,37 @@ describe('Explicit cross-domain linker', function () { delete window.gaplugins }) - describe('links', function () { + describe('when a cross-domain link is clicked', function () { beforeEach(function () { - element = $('') + element = $('') }) it('modifies the link href to append cookie_consent parameter "not-engaged" if cookies_preferences_set cookie is "false"', function () { GOVUK.cookie('cookies_preferences_set', 'false') explicitCrossDomainLinks.start(element) - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=not-engaged') + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') + expect(element.attr('href')).toEqual('#?cookie_consent=not-engaged') + expect(window.location.href).toContain('?cookie_consent=not-engaged') }) it('modifies the link href to append cookie_consent parameter "not-engaged" if cookies_preferences_set cookie is not set', function () { GOVUK.cookie('cookies_preferences_set', null) explicitCrossDomainLinks.start(element) - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=not-engaged') + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') + expect(element.attr('href')).toEqual('#?cookie_consent=not-engaged') + expect(window.location.href).toContain('?cookie_consent=not-engaged') }) it('modifies the link href to append cookie_consent parameter "reject" if usage cookies have been rejected', function () { GOVUK.cookie('cookies_preferences_set', 'true') GOVUK.setConsentCookie({ usage: false }) explicitCrossDomainLinks.start(element) - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=reject') + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') + expect(element.attr('href')).toEqual('#?cookie_consent=reject') + expect(window.location.href).toContain('?cookie_consent=reject') }) describe('user has accepted cookies', function () { @@ -73,16 +82,23 @@ describe('Explicit cross-domain linker', function () { trackers = [{ ga_mock: 'foobar' }] explicitCrossDomainLinks.start(element) + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=accept&_ga=abc123') + expect(element.attr('href')).toEqual('#?cookie_consent=accept&_ga=abc123') + expect(window.location.href).toContain('?cookie_consent=accept&_ga=abc123') }) it('modifies the link href to only append cookie_consent "accept" if there are no trackers', function () { GOVUK.cookie('cookies_preferences_set', 'true') GOVUK.setConsentCookie({ usage: true }) trackers = [] + explicitCrossDomainLinks.start(element) - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=accept') + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') + expect(element.attr('href')).toEqual('#?cookie_consent=accept') + expect(window.location.href).toContain('?cookie_consent=accept') }) it('modifies the link href to only append cookie_consent "accept" if ga is not initalised on window', function () { @@ -90,34 +106,15 @@ describe('Explicit cross-domain linker', function () { GOVUK.setConsentCookie({ usage: true }) window.ga = undefined explicitCrossDomainLinks.start(element) - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=accept') - }) - }) - - describe('user has interacted with the cookie banner on the current page', function () { - beforeEach(function () { - GOVUK.cookie('cookies_preferences_set', null) - explicitCrossDomainLinks.start(element) - GOVUK.cookie('cookies_preferences_set', 'true') - }) - it('modifies the link href to append cookie_consent parameter "accept" if the cookie-consent event was fired', function () { - GOVUK.setConsentCookie({ usage: true }) - window.ga = undefined - window.GOVUK.triggerEvent(window, 'cookie-consent') - - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=accept') - }) - - it('modifies the link href to append cookie_consent parameter "reject" if the cookie-reject event was fired', function () { - GOVUK.setConsentCookie({ usage: false }) - window.GOVUK.triggerEvent(window, 'cookie-reject') - - expect(element.attr('href')).toEqual('/somewhere?cookie_consent=reject') + expect(element.attr('href')).toEqual('#') + window.GOVUK.triggerEvent(element[0], 'click') + expect(element.attr('href')).toEqual('#?cookie_consent=accept') + expect(window.location.href).toContain('?cookie_consent=accept') }) }) }) - describe('forms', function () { + describe('when a cross-domain form is submitted', function () { beforeEach(function () { element = $('
' + '' + @@ -128,12 +125,17 @@ describe('Explicit cross-domain linker', function () { it('modifies the form action to append cookie_consent parameter "not-engaged" if cookies_preferences_set cookie is "false"', function () { GOVUK.cookie('cookies_preferences_set', 'false') explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') + expect(element.attr('action')).toEqual('/somewhere?cookie_consent=not-engaged') }) it('modifies the form action to append cookie_consent parameter "not-engaged" if cookies_preferences_set cookie is not set', function () { GOVUK.cookie('cookies_preferences_set', null) explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') expect(element.attr('action')).toEqual('/somewhere?cookie_consent=not-engaged') }) @@ -141,6 +143,8 @@ describe('Explicit cross-domain linker', function () { GOVUK.cookie('cookies_preferences_set', 'true') GOVUK.setConsentCookie({ usage: false }) explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') expect(element.attr('action')).toEqual('/somewhere?cookie_consent=reject') }) @@ -150,6 +154,8 @@ describe('Explicit cross-domain linker', function () { GOVUK.setConsentCookie({ usage: true }) trackers = [{ ga_mock: 'foobar' }] explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') expect(element.attr('action')).toEqual('/somewhere?cookie_consent=accept&_ga=abc123') }) @@ -158,6 +164,8 @@ describe('Explicit cross-domain linker', function () { GOVUK.setConsentCookie({ usage: true }) trackers = [] explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') expect(element.attr('action')).toEqual('/somewhere?cookie_consent=accept') }) @@ -166,30 +174,10 @@ describe('Explicit cross-domain linker', function () { GOVUK.setConsentCookie({ usage: true }) window.ga = undefined explicitCrossDomainLinks.start(element) + expect(element.attr('action')).toEqual('/somewhere') + window.GOVUK.triggerEvent(element[0], 'submit') expect(element.attr('action')).toEqual('/somewhere?cookie_consent=accept') }) }) - - describe('user has interacted with the cookie banner on the current page', function () { - beforeEach(function () { - GOVUK.cookie('cookies_preferences_set', null) - explicitCrossDomainLinks.start(element) - GOVUK.cookie('cookies_preferences_set', 'true') - }) - it('modifies the form action to append cookie_consent parameter "accept" if the cookie-consent event was fired', function () { - GOVUK.setConsentCookie({ usage: true }) - window.ga = undefined - window.GOVUK.triggerEvent(window, 'cookie-consent') - - expect(element.attr('action')).toEqual('/somewhere?cookie_consent=accept') - }) - - it('modifies the form action to append cookie_consent parameter "reject" if the cookie-reject event was fired', function () { - GOVUK.setConsentCookie({ usage: false }) - window.GOVUK.triggerEvent(window, 'cookie-reject') - - expect(element.attr('action')).toEqual('/somewhere?cookie_consent=reject') - }) - }) }) }) diff --git a/spec/javascripts/govuk_publishing_components/modules.spec.js b/spec/javascripts/govuk_publishing_components/modules.spec.js index 9b74f1df74..aaa4f27774 100644 --- a/spec/javascripts/govuk_publishing_components/modules.spec.js +++ b/spec/javascripts/govuk_publishing_components/modules.spec.js @@ -140,20 +140,6 @@ describe('GOVUK Modules', function () { } GOVUK.Modules.TestCookieDependencyModule = TestCookieDependencyModule - // GOV.UK Frontend Module that depends on rejected cookies to start - function TestCookieRejectDependencyModule (element) { - this.element = element - } - TestCookieRejectDependencyModule.prototype.init = function () { - this.startModule = this.startModule.bind(this) - window.addEventListener('cookie-reject', this.startModule) - } - TestCookieRejectDependencyModule.prototype.startModule = function () { - window.removeEventListener('cookie-reject', this.startModule) - callbackFrontendModule(this.element) - } - GOVUK.Modules.TestCookieRejectDependencyModule = TestCookieRejectDependencyModule - container = $('
') }) @@ -165,7 +151,6 @@ describe('GOVUK Modules', function () { delete GOVUK.Modules.GovukTestAlertPublishingAndFrontendModule delete GOVUK.Modules.TestAlertPublishingAndFrontendModule delete GOVUK.Modules.TestCookieDependencyModule - delete GOVUK.Modules.TestCookieRejectDependencyModule container.remove() }) @@ -243,17 +228,6 @@ describe('GOVUK Modules', function () { expect(callbackFrontendModule.calls.count()).toBe(1) }) - it('starts delayed modules once cookies have been rejected', function () { - var module = $('
') - container.append(module) - $('body').append(container) - - GOVUK.modules.start(container) - expect(callbackFrontendModule.calls.count()).toBe(0) - window.GOVUK.triggerEvent(window, 'cookie-reject') - expect(callbackFrontendModule.calls.count()).toBe(1) - }) - it('starts multiple delayed modules once cookies have been consented', function () { var module1 = $('
') var module2 = $('
')