govuk_publishing_components 32.1.0 → 33.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -1
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +175 -0
  4. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-ecommerce-tracker.js +4 -4
  5. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +5 -13
  6. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +80 -309
  7. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js +2 -2
  8. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +140 -0
  9. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/init-ga4.js +3 -0
  10. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4.js +1 -0
  11. data/app/assets/javascripts/govuk_publishing_components/components/accordion.js +12 -1
  12. data/app/assets/javascripts/govuk_publishing_components/components/single-page-notification-button.js +24 -8
  13. data/app/assets/javascripts/govuk_publishing_components/components/step-by-step-nav.js +22 -1
  14. data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +140 -191
  15. data/app/assets/stylesheets/govuk_publishing_components/components/_big-number.scss +2 -5
  16. data/app/assets/stylesheets/govuk_publishing_components/components/_image-card.scss +1 -5
  17. data/app/assets/stylesheets/govuk_publishing_components/components/_input.scss +3 -5
  18. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +10 -30
  19. data/app/assets/stylesheets/govuk_publishing_components/components/_search.scss +0 -7
  20. data/app/views/govuk_publishing_components/components/_accordion.html.erb +14 -1
  21. data/app/views/govuk_publishing_components/components/_error_summary.html.erb +27 -26
  22. data/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb +2 -2
  23. data/app/views/govuk_publishing_components/components/_phase_banner.html.erb +1 -1
  24. data/app/views/govuk_publishing_components/components/_share_links.html.erb +11 -13
  25. data/app/views/govuk_publishing_components/components/_single_page_notification_button.html.erb +1 -1
  26. data/app/views/govuk_publishing_components/components/_step_by_step_nav.html.erb +4 -1
  27. data/app/views/govuk_publishing_components/components/docs/accordion.yml +15 -3
  28. data/app/views/govuk_publishing_components/components/docs/button.yml +10 -0
  29. data/app/views/govuk_publishing_components/components/docs/share_links.yml +59 -30
  30. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +10 -1
  31. data/app/views/govuk_publishing_components/components/docs/step_by_step_nav.yml +34 -0
  32. data/app/views/govuk_publishing_components/components/feedback/_problem_form.html.erb +1 -1
  33. data/app/views/govuk_publishing_components/components/feedback/_survey_signup_form.html.erb +1 -1
  34. data/app/views/govuk_publishing_components/components/feedback/_yes_no_banner.html.erb +3 -3
  35. data/lib/govuk_publishing_components/presenters/button_helper.rb +9 -2
  36. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +25 -1
  37. data/lib/govuk_publishing_components/version.rb +1 -1
  38. data/node_modules/axe-core/axe.js +4559 -4673
  39. data/node_modules/axe-core/axe.min.js +2 -2
  40. data/node_modules/axe-core/package.json +2 -2
  41. data/node_modules/axe-core/sri-history.json +4 -0
  42. data/node_modules/govuk-frontend/README.md +1 -2
  43. data/node_modules/govuk-frontend/govuk/all.js +1398 -273
  44. data/node_modules/govuk-frontend/govuk/common/closest-attribute-value.js +70 -0
  45. data/node_modules/govuk-frontend/govuk/common/index.js +172 -0
  46. data/node_modules/govuk-frontend/govuk/common/normalise-dataset.js +373 -0
  47. data/node_modules/govuk-frontend/govuk/common.js +138 -3
  48. data/node_modules/govuk-frontend/govuk/components/accordion/accordion.js +753 -25
  49. data/node_modules/govuk-frontend/govuk/components/accordion/fixtures.json +54 -22
  50. data/node_modules/govuk-frontend/govuk/components/accordion/macro-options.json +36 -0
  51. data/node_modules/govuk-frontend/govuk/components/accordion/template.njk +7 -1
  52. data/node_modules/govuk-frontend/govuk/components/back-link/fixtures.json +12 -12
  53. data/node_modules/govuk-frontend/govuk/components/breadcrumbs/fixtures.json +22 -22
  54. data/node_modules/govuk-frontend/govuk/components/button/_index.scss +23 -5
  55. data/node_modules/govuk-frontend/govuk/components/button/button.js +365 -107
  56. data/node_modules/govuk-frontend/govuk/components/button/fixtures.json +85 -66
  57. data/node_modules/govuk-frontend/govuk/components/button/template.njk +1 -1
  58. data/node_modules/govuk-frontend/govuk/components/character-count/_index.scss +9 -0
  59. data/node_modules/govuk-frontend/govuk/components/character-count/character-count.js +1033 -121
  60. data/node_modules/govuk-frontend/govuk/components/character-count/fixtures.json +112 -36
  61. data/node_modules/govuk-frontend/govuk/components/character-count/macro-options.json +42 -0
  62. data/node_modules/govuk-frontend/govuk/components/character-count/template.njk +27 -3
  63. data/node_modules/govuk-frontend/govuk/components/checkboxes/checkboxes.js +30 -2
  64. data/node_modules/govuk-frontend/govuk/components/checkboxes/fixtures.json +96 -93
  65. data/node_modules/govuk-frontend/govuk/components/cookie-banner/fixtures.json +46 -46
  66. data/node_modules/govuk-frontend/govuk/components/date-input/fixtures.json +50 -50
  67. data/node_modules/govuk-frontend/govuk/components/details/details.js +43 -13
  68. data/node_modules/govuk-frontend/govuk/components/details/fixtures.json +20 -20
  69. data/node_modules/govuk-frontend/govuk/components/error-message/fixtures.json +20 -20
  70. data/node_modules/govuk-frontend/govuk/components/error-summary/error-summary.js +268 -6
  71. data/node_modules/govuk-frontend/govuk/components/error-summary/fixtures.json +44 -35
  72. data/node_modules/govuk-frontend/govuk/components/error-summary/template.njk +25 -21
  73. data/node_modules/govuk-frontend/govuk/components/fieldset/fixtures.json +51 -39
  74. data/node_modules/govuk-frontend/govuk/components/file-upload/fixtures.json +26 -26
  75. data/node_modules/govuk-frontend/govuk/components/footer/_index.scss +1 -1
  76. data/node_modules/govuk-frontend/govuk/components/footer/fixtures.json +46 -46
  77. data/node_modules/govuk-frontend/govuk/components/footer/macro-options.json +2 -2
  78. data/node_modules/govuk-frontend/govuk/components/header/fixtures.json +93 -38
  79. data/node_modules/govuk-frontend/govuk/components/header/header.js +6 -0
  80. data/node_modules/govuk-frontend/govuk/components/header/macro-options.json +8 -2
  81. data/node_modules/govuk-frontend/govuk/components/header/template.njk +4 -2
  82. data/node_modules/govuk-frontend/govuk/components/hint/fixtures.json +12 -12
  83. data/node_modules/govuk-frontend/govuk/components/input/fixtures.json +80 -80
  84. data/node_modules/govuk-frontend/govuk/components/inset-text/fixtures.json +12 -12
  85. data/node_modules/govuk-frontend/govuk/components/label/fixtures.json +34 -34
  86. data/node_modules/govuk-frontend/govuk/components/notification-banner/fixtures.json +56 -46
  87. data/node_modules/govuk-frontend/govuk/components/notification-banner/notification-banner.js +252 -2
  88. data/node_modules/govuk-frontend/govuk/components/notification-banner/template.njk +1 -1
  89. data/node_modules/govuk-frontend/govuk/components/pagination/_index.scss +10 -7
  90. data/node_modules/govuk-frontend/govuk/components/pagination/fixtures.json +33 -26
  91. data/node_modules/govuk-frontend/govuk/components/panel/fixtures.json +18 -18
  92. data/node_modules/govuk-frontend/govuk/components/phase-banner/fixtures.json +14 -14
  93. data/node_modules/govuk-frontend/govuk/components/radios/fixtures.json +94 -91
  94. data/node_modules/govuk-frontend/govuk/components/radios/radios.js +30 -2
  95. data/node_modules/govuk-frontend/govuk/components/select/fixtures.json +32 -32
  96. data/node_modules/govuk-frontend/govuk/components/skip-link/fixtures.json +22 -20
  97. data/node_modules/govuk-frontend/govuk/components/skip-link/skip-link.js +10 -4
  98. data/node_modules/govuk-frontend/govuk/components/summary-list/fixtures.json +50 -50
  99. data/node_modules/govuk-frontend/govuk/components/table/_index.scss +1 -1
  100. data/node_modules/govuk-frontend/govuk/components/table/fixtures.json +40 -40
  101. data/node_modules/govuk-frontend/govuk/components/tabs/fixtures.json +29 -29
  102. data/node_modules/govuk-frontend/govuk/components/tabs/tabs.js +28 -0
  103. data/node_modules/govuk-frontend/govuk/components/tag/fixtures.json +28 -28
  104. data/node_modules/govuk-frontend/govuk/components/textarea/fixtures.json +34 -34
  105. data/node_modules/govuk-frontend/govuk/components/warning-text/fixtures.json +14 -14
  106. data/node_modules/govuk-frontend/govuk/core/_section-break.scss +1 -1
  107. data/node_modules/govuk-frontend/govuk/helpers/_colour.scss +2 -2
  108. data/node_modules/govuk-frontend/govuk/helpers/_links.scss +6 -6
  109. data/node_modules/govuk-frontend/govuk/i18n.js +390 -0
  110. data/node_modules/govuk-frontend/govuk/macros/i18n.njk +15 -0
  111. data/node_modules/govuk-frontend/govuk/settings/_all.scss +1 -0
  112. data/node_modules/govuk-frontend/govuk/settings/_colours-palette.scss +12 -0
  113. data/node_modules/govuk-frontend/govuk/settings/_compatibility.scss +26 -0
  114. data/node_modules/govuk-frontend/govuk/settings/_typography-font.scss +23 -0
  115. data/node_modules/govuk-frontend/govuk/settings/_typography-responsive.scss +12 -0
  116. data/node_modules/govuk-frontend/govuk/settings/_warnings.scss +53 -0
  117. data/node_modules/govuk-frontend/govuk/tools/_compatibility.scss +20 -6
  118. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Date/now.js +21 -0
  119. data/node_modules/govuk-frontend/govuk/vendor/polyfills/Element/prototype/dataset.js +300 -0
  120. data/node_modules/govuk-frontend/govuk/vendor/polyfills/String/prototype/trim.js +21 -0
  121. data/node_modules/govuk-frontend/govuk-esm/all.mjs +50 -27
  122. data/node_modules/govuk-frontend/govuk-esm/common/closest-attribute-value.mjs +15 -0
  123. data/node_modules/govuk-frontend/govuk-esm/common/index.mjs +159 -0
  124. data/node_modules/govuk-frontend/govuk-esm/common/normalise-dataset.mjs +58 -0
  125. data/node_modules/govuk-frontend/govuk-esm/common.mjs +6 -28
  126. data/node_modules/govuk-frontend/govuk-esm/components/accordion/accordion.mjs +113 -43
  127. data/node_modules/govuk-frontend/govuk-esm/components/button/button.mjs +67 -30
  128. data/node_modules/govuk-frontend/govuk-esm/components/character-count/character-count.mjs +325 -123
  129. data/node_modules/govuk-frontend/govuk-esm/components/checkboxes/checkboxes.mjs +9 -3
  130. data/node_modules/govuk-frontend/govuk-esm/components/details/details.mjs +22 -8
  131. data/node_modules/govuk-frontend/govuk-esm/components/error-summary/error-summary.mjs +48 -6
  132. data/node_modules/govuk-frontend/govuk-esm/components/header/header.mjs +6 -0
  133. data/node_modules/govuk-frontend/govuk-esm/components/notification-banner/notification-banner.mjs +32 -2
  134. data/node_modules/govuk-frontend/govuk-esm/components/radios/radios.mjs +9 -3
  135. data/node_modules/govuk-frontend/govuk-esm/components/skip-link/skip-link.mjs +10 -4
  136. data/node_modules/govuk-frontend/govuk-esm/components/tabs/tabs.mjs +8 -2
  137. data/node_modules/govuk-frontend/govuk-esm/i18n.mjs +380 -0
  138. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Date/now.mjs +13 -0
  139. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/Element/prototype/dataset.mjs +68 -0
  140. data/node_modules/govuk-frontend/govuk-esm/vendor/polyfills/String/prototype/trim.mjs +13 -0
  141. data/node_modules/govuk-frontend/govuk-prototype-kit/init.js +7 -0
  142. data/node_modules/govuk-frontend/govuk-prototype-kit/init.scss +12 -0
  143. data/node_modules/govuk-frontend/govuk-prototype-kit.config.json +138 -7
  144. data/node_modules/govuk-frontend/package.json +1 -1
  145. metadata +22 -3
@@ -1,331 +1,102 @@
1
- // = require govuk/vendor/polyfills/Element/prototype/closest.js
1
+ //= require ../vendor/polyfills/closest.js
2
2
  window.GOVUK = window.GOVUK || {}
3
- window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}
4
- window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analyticsModules || {};
3
+ window.GOVUK.Modules = window.GOVUK.Modules || {};
5
4
 
6
- (function (analyticsModules) {
5
+ (function (Modules) {
7
6
  'use strict'
8
7
 
9
- var Ga4LinkTracker = {
10
- init: function (config) {
11
- if (window.dataLayer) {
12
- config = config || {}
13
- this.internalDomains = config.internalDomains || []
14
- this.internalDomains.push(this.getHostname())
15
- this.appendDomainsWithoutWWW(this.internalDomains)
16
- this.internalDownloadPaths = config.internalDownloadPaths || ['/government/uploads/']
17
- this.dedicatedDownloadDomains = config.dedicatedDownloadDomains || ['assets.publishing.service.gov.uk']
18
- this.appendDomainsWithoutWWW(this.dedicatedDownloadDomains)
19
- this.handleClick = this.handleClick.bind(this)
20
- this.handleMousedown = this.handleMousedown.bind(this)
21
-
22
- if (!config.disableListeners) {
23
- document.querySelector('body').addEventListener('click', this.handleClick)
24
- document.querySelector('body').addEventListener('contextmenu', this.handleClick)
25
- document.querySelector('body').addEventListener('mousedown', this.handleMousedown)
26
- }
27
- }
28
- },
29
-
30
- stopTracking: function () {
31
- document.querySelector('body').removeEventListener('click', this.handleClick)
32
- document.querySelector('body').removeEventListener('contextmenu', this.handleClick)
33
- document.querySelector('body').removeEventListener('mousedown', this.handleMousedown)
34
- },
35
-
36
- handleClick: function (event) {
37
- var element = event.target
38
-
39
- if (element.tagName !== 'A') {
40
- element = element.closest('a')
41
- }
42
-
43
- if (!element) {
44
- return
45
- }
46
-
47
- var href = element.getAttribute('href')
48
-
49
- if (!href) {
50
- return
51
- }
52
- var clickData = {}
53
- var linkAttributes = element.getAttribute('data-ga4-link')
54
- if (linkAttributes) {
55
- clickData = JSON.parse(linkAttributes)
56
-
57
- /* Since external links can't be determined in the template, we use populated-via-js as a signal
58
- for our JavaScript to determine this value. */
59
- if (clickData.external === 'populated-via-js' && clickData.url) {
60
- clickData.external = this.isExternalLink(clickData.url) ? 'true' : 'false'
61
- }
62
-
63
- if (clickData.method === 'populated-via-js') {
64
- clickData.method = this.getClickType(event)
65
- }
66
-
67
- if (clickData.index) {
68
- clickData.index = parseInt(clickData.index)
69
- }
8
+ function Ga4LinkTracker (module) {
9
+ this.module = module
10
+ this.trackingTrigger = 'data-ga4-link' // elements with this attribute get tracked
11
+ this.trackLinksOnly = this.module.hasAttribute('data-ga4-track-links-only')
12
+ this.limitToElementClass = this.module.getAttribute('data-ga4-limit-to-element-class')
13
+ }
70
14
 
71
- if (clickData.index_total) {
72
- clickData.index_total = parseInt(clickData.index_total)
73
- }
74
- } else if (this.isMailToLink(href)) {
75
- clickData.event_name = 'navigation'
76
- clickData.type = 'email'
77
- clickData.external = 'true'
78
- clickData.url = href
79
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
80
- clickData.method = this.getClickType(event)
81
- } else if (this.isDownloadLink(href)) {
82
- clickData.event_name = 'file_download'
83
- clickData.type = this.isPreviewLink(href) ? 'preview' : 'generic download'
84
- clickData.external = this.isExternalLink(href) ? 'true' : 'false'
85
- clickData.url = href
86
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
87
- clickData.method = this.getClickType(event)
88
- } else if (this.isExternalLink(href)) {
89
- clickData.event_name = 'navigation'
90
- clickData.type = 'generic link'
91
- clickData.external = 'true'
92
- clickData.url = href
93
- clickData.text = this.removeLinesAndExtraSpaces(element.textContent)
94
- clickData.method = this.getClickType(event)
95
- }
15
+ Ga4LinkTracker.prototype.init = function () {
16
+ var consentCookie = window.GOVUK.getConsentCookie()
96
17
 
97
- if (Object.keys(clickData).length > 0) {
98
- if (clickData.url) {
99
- clickData.url = this.removeCrossDomainParams(clickData.url)
100
- clickData.link_domain = this.populateLinkDomain(clickData.url)
101
- clickData.link_path_parts = this.populateLinkPathParts(clickData.url)
102
- }
18
+ if (consentCookie && consentCookie.settings) {
19
+ this.startModule()
20
+ } else {
21
+ this.startModule = this.startModule.bind(this)
22
+ window.addEventListener('cookie-consent', this.startModule)
23
+ }
24
+ }
103
25
 
104
- var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
105
- schema.event = 'event_data'
26
+ // triggered by cookie-consent event, which happens when users consent to cookies
27
+ Ga4LinkTracker.prototype.startModule = function () {
28
+ if (window.dataLayer) {
29
+ this.handleClick = this.handleClick.bind(this)
30
+ this.handleMousedown = this.handleMousedown.bind(this)
106
31
 
107
- // get attributes from the clickData object to send to GA
108
- // only allow it if it already exists in the schema
109
- for (var property in clickData) {
110
- if (property in schema.event_data) {
111
- schema.event_data[property] = clickData[property]
112
- }
113
- }
32
+ this.module.addEventListener('click', this.handleClick)
33
+ this.module.addEventListener('contextmenu', this.handleClick)
34
+ this.module.addEventListener('mousedown', this.handleMousedown)
35
+ }
36
+ }
114
37
 
115
- window.GOVUK.analyticsGa4.core.sendData(schema)
38
+ Ga4LinkTracker.prototype.handleClick = function (event) {
39
+ var target = event.target
40
+ if (!this.trackLinksOnly) {
41
+ this.trackClick(event)
42
+ } else if (this.trackLinksOnly && target.closest('a')) {
43
+ if (!this.limitToElementClass) {
44
+ this.trackClick(event)
45
+ } else if (target.closest('.' + this.limitToElementClass)) {
46
+ this.trackClick(event)
116
47
  }
117
- },
48
+ }
49
+ }
118
50
 
119
- populateLinkPathParts: function (href) {
120
- var path = ''
121
- if (this.hrefIsRelative(href) || this.isMailToLink(href)) {
122
- path = href
123
- } else {
124
- // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
125
- path = href.replace(/^(http:||https:)?(\/\/)([^\/]*)/, '') // eslint-disable-line no-useless-escape
126
- }
51
+ Ga4LinkTracker.prototype.handleMousedown = function (event) {
52
+ // 1 = middle mouse button
53
+ if (event.button === 1) {
54
+ this.handleClick(event)
55
+ }
56
+ }
127
57
 
128
- if (path === '/' || path.length === 0) {
58
+ Ga4LinkTracker.prototype.trackClick = function (event) {
59
+ var target = window.GOVUK.analyticsGa4.core.trackFunctions.findTrackingAttributes(event.target, this.trackingTrigger)
60
+ if (target) {
61
+ var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
62
+
63
+ try {
64
+ var data = target.getAttribute(this.trackingTrigger)
65
+ data = JSON.parse(data)
66
+ } catch (e) {
67
+ // if there's a problem with the config, don't start the tracker
68
+ console.error('GA4 configuration error: ' + e.message, window.location)
129
69
  return
130
70
  }
131
71
 
132
- /*
133
- This will create an object with 5 keys that are indexes ("1", "2", etc.)
134
- The values will be each part of the link path split every 100 characters, or undefined.
135
- For example: {"1": "/hello/world/etc...", "2": "/more/path/text...", "3": undefined, "4": undefined, "5": undefined}
136
- Undefined values are needed to override the persistent object in GTM so that any values from old pushes are overwritten.
137
- */
138
- var parts = path.match(/.{1,100}/g)
139
- var obj = {}
140
- for (var i = 0; i < 5; i++) {
141
- obj[(i + 1).toString()] = parts[i]
142
- }
143
- return obj
144
- },
145
-
146
- populateLinkDomain: function (href) {
147
- // We always want mailto links to have an undefined link_domain
148
- if (this.isMailToLink(href)) {
149
- return undefined
150
- }
151
-
152
- if (this.hrefIsRelative(href)) {
153
- return this.getProtocol() + '//' + this.getHostname()
154
- } else {
155
- // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk
156
- var domainRegex = /^(http:||https:)?(\/\/)([^\/]*)/ // eslint-disable-line no-useless-escape
157
- var domain = domainRegex.exec(href)[0]
158
- return domain
159
- }
160
- },
161
-
162
- appendDomainsWithoutWWW: function (domainsArrays) {
163
- // Add domains with www. removed, in case site hrefs are marked up without www. included.
164
- for (var i = 0; i < domainsArrays.length; i++) {
165
- var domain = domainsArrays[i]
166
- if (this.stringStartsWith(domain, 'www.')) {
167
- var domainWithoutWww = domain.replace('www.', '')
168
- domainsArrays.push(domainWithoutWww)
72
+ schema.event = 'event_data'
73
+ var text = data.text || event.target.textContent
74
+ data.text = window.GOVUK.analyticsGa4.core.trackFunctions.removeLinesAndExtraSpaces(text)
75
+ data.url = window.GOVUK.analyticsGa4.core.trackFunctions.removeCrossDomainParams(this.findLink(event.target).getAttribute('href'))
76
+ data.link_domain = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkDomain(data.url)
77
+ data.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(data.url)
78
+ data.method = window.GOVUK.analyticsGa4.core.trackFunctions.getClickType(event)
79
+ data.external = window.GOVUK.analyticsGa4.core.trackFunctions.isExternalLink(data.url) ? 'true' : 'false'
80
+
81
+ // get attributes from the data attribute to send to GA
82
+ // only allow it if it already exists in the schema
83
+ for (var property in data) {
84
+ if (property in schema.event_data) {
85
+ schema.event_data[property] = data[property]
169
86
  }
170
87
  }
171
- },
172
-
173
- removeLinesAndExtraSpaces: function (text) {
174
- text = text.trim()
175
- text = text.replace(/(\r\n|\n|\r)/gm, ' ') // Replace line breaks with 1 space
176
- text = text.replace(/\s+/g, ' ') // Replace instances of 2+ spaces with 1 space
177
- return text
178
- },
179
-
180
- getClickType: function (event) {
181
- switch (event.type) {
182
- case 'click':
183
- if (event.ctrlKey) {
184
- return 'ctrl click'
185
- } else if (event.metaKey) {
186
- return 'command/win click'
187
- } else if (event.shiftKey) {
188
- return 'shift click'
189
- } else {
190
- return 'primary click'
191
- }
192
- case 'mousedown':
193
- return 'middle click'
194
- case 'contextmenu':
195
- return 'secondary click'
196
- }
197
- },
198
-
199
- handleMousedown: function (event) {
200
- // 1 = middle mouse button
201
- if (event.button === 1) {
202
- this.handleClick(event)
203
- }
204
- },
205
-
206
- isMailToLink: function (href) {
207
- return href.substring(0, 7) === 'mailto:'
208
- },
209
-
210
- isDownloadLink: function (href) {
211
- if (this.isInternalLink(href) && this.hrefPointsToDownloadPath(href)) {
212
- return true
213
- }
214
-
215
- var result = false
216
- for (var i = 0; i < this.dedicatedDownloadDomains.length; i++) {
217
- var downloadDomain = this.dedicatedDownloadDomains[i]
218
- if (this.hrefPointsToDomain(href, downloadDomain)) {
219
- result = true
220
- }
221
- }
222
- return result
223
- },
224
-
225
- isInternalLink: function (href) {
226
- if (this.hrefIsRelative(href) || this.hrefIsAnchor(href)) {
227
- return true
228
- }
229
- var result = false
230
- for (var i = 0; i < this.internalDomains.length; i++) {
231
- var internalDomain = this.internalDomains[i]
232
- if (this.hrefPointsToDomain(href, internalDomain)) {
233
- result = true
234
- }
235
- }
236
- return result
237
- },
238
-
239
- isExternalLink: function (href) {
240
- return !this.isInternalLink(href)
241
- },
242
88
 
243
- isPreviewLink: function (href) {
244
- /* Regex looks for:
245
- 1. The file extension period (the character '.')
246
- 2. any alphanumeric characters (so we can match any file type such as jpg, pdf, mp4.)
247
- 3. the presence of '/preview'.
248
- For example, .csv/preview or .mp4/preview will be matched.
249
- Regex is used over JS string methods as this should work with anchor links, query string parameters and files that may have 'preview' in their name.
250
- */
251
- var previewRegex = /\.\w+\/preview/i
252
- return previewRegex.test(href)
253
- },
254
-
255
- hrefPointsToDomain: function (href, domain) {
256
- /* Add a trailing slash to prevent an edge case such
257
- as the href www.gov.uk.domain.co.uk being detected as an internal link,
258
- if we were checking for 'www.gov.uk' instead of 'www.gov.uk/' */
259
- if (domain.substring(domain.length) !== '/') {
260
- domain = domain + '/'
261
- }
262
-
263
- /* If the href doesn't end in a slash, we add one.
264
- This fixes an edge case where the <a href> is exactly `https://www.gov.uk`
265
- but these checks would only look for `https://www.gov.uk/` */
266
- if (href.substring(href.length) !== '/') {
267
- href = href + '/'
268
- }
269
- var httpDomain = 'http://' + domain
270
- var httpsDomain = 'https://' + domain
271
- var schemaRelativeDomain = '//' + domain
272
- return this.stringStartsWith(href, domain) ||
273
- this.stringStartsWith(href, httpDomain) ||
274
- this.stringStartsWith(href, httpsDomain) ||
275
- this.stringStartsWith(href, schemaRelativeDomain)
276
- },
277
-
278
- hrefPointsToDownloadPath: function (href) {
279
- var result = false
280
- for (var i = 0; i < this.internalDownloadPaths.length; i++) {
281
- var internalDownloadPath = this.internalDownloadPaths[i]
282
- if (href.indexOf(internalDownloadPath) !== -1) {
283
- result = true
284
- }
285
- }
286
- return result
287
- },
288
-
289
- stringStartsWith: function (string, stringToFind) {
290
- return string.substring(0, stringToFind.length) === stringToFind
291
- },
292
-
293
- stringEndsWith: function (string, stringToFind) {
294
- return string.substring(string.length - stringToFind.length, string.length) === stringToFind
295
- },
296
-
297
- hrefIsRelative: function (href) {
298
- // Checks that a link is relative, but is not a protocol relative url
299
- return href[0] === '/' && href[1] !== '/'
300
- },
301
-
302
- hrefIsAnchor: function (href) {
303
- return href[0] === '#'
304
- },
305
-
306
- getHostname: function () {
307
- return window.location.hostname
308
- },
309
-
310
- getProtocol: function () {
311
- return window.location.protocol
312
- },
313
-
314
- removeCrossDomainParams: function (href) {
315
- if (href.indexOf('_ga') !== -1 || href.indexOf('_gl') !== -1) {
316
- // _ga & _gl are values needed for cross domain tracking, but we don't want them included in our click tracking.
317
- href = href.replaceAll(/_g[al]=([^&]*)/g, '')
89
+ window.GOVUK.analyticsGa4.core.sendData(schema)
90
+ }
91
+ }
318
92
 
319
- // The following code cleans up inconsistencies such as gov.uk/&&, gov.uk/?&hello=world, gov.uk/?, and gov.uk/&.
320
- href = href.replaceAll(/(&&)+/g, '&')
321
- href = href.replace('?&', '?')
322
- if (this.stringEndsWith(href, '?') || this.stringEndsWith(href, '&')) {
323
- href = href.substring(0, href.length - 1)
324
- }
325
- }
326
- return href
93
+ Ga4LinkTracker.prototype.findLink = function (target) {
94
+ if (target.tagName === 'A') {
95
+ return target
96
+ } else {
97
+ return target.closest('a')
327
98
  }
328
99
  }
329
100
 
330
- analyticsModules.Ga4LinkTracker = Ga4LinkTracker
331
- })(window.GOVUK.analyticsGa4.analyticsModules)
101
+ Modules.Ga4LinkTracker = Ga4LinkTracker
102
+ })(window.GOVUK.Modules)
@@ -27,10 +27,10 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
27
27
  schema_name: this.getMetaContent('schema-name'),
28
28
  content_id: this.getMetaContent('content-id'),
29
29
 
30
- section: this.getMetaContent('section'),
30
+ browse_topic: this.getMetaContent('section'),
31
31
  taxon_slug: this.getMetaContent('taxon-slug'),
32
32
  taxon_id: this.getMetaContent('taxon-id'),
33
- themes: this.getMetaContent('themes'),
33
+ taxonomy_level1: this.getMetaContent('themes'),
34
34
  taxon_slugs: this.getMetaContent('taxon-slugs'),
35
35
  taxon_ids: this.getMetaContent('taxon-ids'),
36
36
 
@@ -0,0 +1,140 @@
1
+ // = require govuk/vendor/polyfills/Element/prototype/closest.js
2
+ window.GOVUK = window.GOVUK || {}
3
+ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}
4
+ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analyticsModules || {};
5
+
6
+ (function (analyticsModules) {
7
+ 'use strict'
8
+
9
+ var Ga4SpecialistLinkTracker = {
10
+ init: function (config) {
11
+ if (window.dataLayer) {
12
+ config = config || {}
13
+ this.internalDownloadPaths = config.internalDownloadPaths || ['/government/uploads/']
14
+ this.dedicatedDownloadDomains = config.dedicatedDownloadDomains || ['assets.publishing.service.gov.uk']
15
+ window.GOVUK.analyticsGa4.core.trackFunctions.appendDomainsWithoutWWW(this.dedicatedDownloadDomains)
16
+ this.handleClick = this.handleClick.bind(this)
17
+ this.handleMousedown = this.handleMousedown.bind(this)
18
+
19
+ document.querySelector('body').addEventListener('click', this.handleClick)
20
+ document.querySelector('body').addEventListener('contextmenu', this.handleClick)
21
+ document.querySelector('body').addEventListener('mousedown', this.handleMousedown)
22
+ }
23
+ },
24
+
25
+ stopTracking: function () {
26
+ document.querySelector('body').removeEventListener('click', this.handleClick)
27
+ document.querySelector('body').removeEventListener('contextmenu', this.handleClick)
28
+ document.querySelector('body').removeEventListener('mousedown', this.handleMousedown)
29
+ },
30
+
31
+ handleClick: function (event) {
32
+ var element = event.target
33
+
34
+ if (element.tagName !== 'A') {
35
+ element = element.closest('a')
36
+ }
37
+
38
+ if (!element) {
39
+ return
40
+ }
41
+
42
+ // don't track this link if it's already being tracked by the other link tracker
43
+ if (element.closest('[data-ga4-link]')) {
44
+ return
45
+ }
46
+
47
+ var href = element.getAttribute('href')
48
+
49
+ if (!href) {
50
+ return
51
+ }
52
+ var clickData = {}
53
+ if (window.GOVUK.analyticsGa4.core.trackFunctions.isMailToLink(href)) {
54
+ clickData.event_name = 'navigation'
55
+ clickData.type = 'email'
56
+ clickData.external = 'true'
57
+ } else if (this.isDownloadLink(href)) {
58
+ clickData.event_name = 'file_download'
59
+ clickData.type = this.isPreviewLink(href) ? 'preview' : 'generic download'
60
+ clickData.external = window.GOVUK.analyticsGa4.core.trackFunctions.isExternalLink(href) ? 'true' : 'false'
61
+ } else if (window.GOVUK.analyticsGa4.core.trackFunctions.isExternalLink(href)) {
62
+ clickData.event_name = 'navigation'
63
+ clickData.type = 'generic link'
64
+ clickData.external = 'true'
65
+ }
66
+
67
+ if (Object.keys(clickData).length > 0) {
68
+ clickData.url = href
69
+ if (clickData.url) {
70
+ clickData.url = window.GOVUK.analyticsGa4.core.trackFunctions.removeCrossDomainParams(clickData.url)
71
+ clickData.link_domain = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkDomain(clickData.url)
72
+ clickData.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(clickData.url)
73
+ }
74
+
75
+ clickData.text = window.GOVUK.analyticsGa4.core.trackFunctions.removeLinesAndExtraSpaces(element.textContent)
76
+ clickData.method = window.GOVUK.analyticsGa4.core.trackFunctions.getClickType(event)
77
+
78
+ var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema()
79
+ schema.event = 'event_data'
80
+
81
+ // get attributes from the clickData object to send to GA
82
+ // only allow it if it already exists in the schema
83
+ for (var property in clickData) {
84
+ if (property in schema.event_data) {
85
+ schema.event_data[property] = clickData[property]
86
+ }
87
+ }
88
+
89
+ window.GOVUK.analyticsGa4.core.sendData(schema)
90
+ }
91
+ },
92
+
93
+ handleMousedown: function (event) {
94
+ // 1 = middle mouse button
95
+ if (event.button === 1) {
96
+ this.handleClick(event)
97
+ }
98
+ },
99
+
100
+ isDownloadLink: function (href) {
101
+ if (window.GOVUK.analyticsGa4.core.trackFunctions.isInternalLink(href) && this.hrefPointsToDownloadPath(href)) {
102
+ return true
103
+ }
104
+
105
+ var result = false
106
+ for (var i = 0; i < this.dedicatedDownloadDomains.length; i++) {
107
+ var downloadDomain = this.dedicatedDownloadDomains[i]
108
+ if (window.GOVUK.analyticsGa4.core.trackFunctions.hrefPointsToDomain(href, downloadDomain)) {
109
+ result = true
110
+ }
111
+ }
112
+ return result
113
+ },
114
+
115
+ isPreviewLink: function (href) {
116
+ /* Regex looks for:
117
+ 1. The file extension period (the character '.')
118
+ 2. any alphanumeric characters (so we can match any file type such as jpg, pdf, mp4.)
119
+ 3. the presence of '/preview'.
120
+ For example, .csv/preview or .mp4/preview will be matched.
121
+ Regex is used over JS string methods as this should work with anchor links, query string parameters and files that may have 'preview' in their name.
122
+ */
123
+ var previewRegex = /\.\w+\/preview/i
124
+ return previewRegex.test(href)
125
+ },
126
+
127
+ hrefPointsToDownloadPath: function (href) {
128
+ var result = false
129
+ for (var i = 0; i < this.internalDownloadPaths.length; i++) {
130
+ var internalDownloadPath = this.internalDownloadPaths[i]
131
+ if (href.indexOf(internalDownloadPath) !== -1) {
132
+ result = true
133
+ }
134
+ }
135
+ return result
136
+ }
137
+ }
138
+
139
+ analyticsModules.Ga4SpecialistLinkTracker = Ga4SpecialistLinkTracker
140
+ })(window.GOVUK.analyticsGa4.analyticsModules)
@@ -7,6 +7,9 @@ var initFunction = function () {
7
7
  var consentCookie = window.GOVUK.getConsentCookie()
8
8
 
9
9
  if (consentCookie && consentCookie.usage) {
10
+ window.GOVUK.analyticsGa4.vars.internalDomains = []
11
+ window.GOVUK.analyticsGa4.vars.internalDomains.push(window.GOVUK.analyticsGa4.core.trackFunctions.getHostname())
12
+ window.GOVUK.analyticsGa4.core.trackFunctions.appendDomainsWithoutWWW(window.GOVUK.analyticsGa4.vars.internalDomains)
10
13
  window.GOVUK.analyticsGa4.core.load()
11
14
 
12
15
  var analyticsModules = window.GOVUK.analyticsGa4.analyticsModules
@@ -3,6 +3,7 @@
3
3
  //= require ./analytics-ga4/ga4-schemas
4
4
  //= require ./analytics-ga4/pii-remover
5
5
  //= require ./analytics-ga4/ga4-page-views
6
+ //= require ./analytics-ga4/ga4-specialist-link-tracker
6
7
  //= require ./analytics-ga4/ga4-link-tracker
7
8
  //= require ./analytics-ga4/ga4-event-tracker
8
9
  //= require ./analytics-ga4/ga4-ecommerce-tracker
@@ -43,9 +43,10 @@ window.GOVUK.Modules.GovukAccordion = window.GOVUKFrontend.Accordion;
43
43
 
44
44
  // look for data attributes to put onto the 'show/hide all' link
45
45
  var showAllAttributes = this.$module.getAttribute('data-show-all-attributes')
46
+ var showAll
46
47
  if (showAllAttributes) {
47
48
  try {
48
- var showAll = this.$module.querySelector(this.showAllControls)
49
+ showAll = this.$module.querySelector(this.showAllControls)
49
50
  var values = JSON.parse(showAllAttributes)
50
51
  var keys = Object.keys(values)
51
52
  for (var i = 0; i < keys.length; i++) {
@@ -55,6 +56,16 @@ window.GOVUK.Modules.GovukAccordion = window.GOVUKFrontend.Accordion;
55
56
  console.error('Could not read accordion data attributes error: ' + e.message, window.location)
56
57
  }
57
58
  }
59
+
60
+ // if GA4 is enabled, set attributes on 'show all sections' for tracking using ga4-event-tracker
61
+ var dataModule = this.$module.getAttribute('data-module')
62
+ var isGa4Enabled = dataModule ? dataModule.indexOf('ga4-event-tracker') !== -1 : false
63
+ if (isGa4Enabled) {
64
+ var indexTotal = this.$module.querySelectorAll('.govuk-accordion__section').length
65
+ var showAllAttributesGa4 = { event_name: 'select_content', type: 'accordion', index: 0, index_total: indexTotal }
66
+ showAll = this.$module.querySelector(this.showAllControls)
67
+ showAll.setAttribute('data-ga4-event', JSON.stringify(showAllAttributesGa4))
68
+ }
58
69
  }
59
70
 
60
71
  // Navigate to and open accordions with anchored content on page load if a hash is present
@@ -28,15 +28,31 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
28
28
  if (xhr.readyState === 4) {
29
29
  if (xhr.status === 200) {
30
30
  var responseText = xhr.responseText
31
- // if response text exists and is JSON parse-able, parse the response and get the button html
31
+ // if response text exists and is JSON parse-able, parse the response and update the button html
32
32
  if (responseText && this.responseIsJSON(responseText)) {
33
- var newButton = JSON.parse(responseText).button_html
34
- var html = document.createElement('div')
35
- html.innerHTML = newButton
36
- // test that the html returned contains the button component; if yes, swap the button for the updated version
37
- var responseButtonContainer = html.querySelector('form.gem-c-single-page-notification-button')
38
- if (responseButtonContainer) {
39
- this.$module.parentNode.replaceChild(responseButtonContainer, this.$module)
33
+ var active = JSON.parse(responseText).active
34
+
35
+ var customSubscribeText = this.$module.getAttribute('data-button-text-subscribe')
36
+ var customUnsubscribeText = this.$module.getAttribute('data-button-text-unsubscribe')
37
+ // Only set custom button text if both text items are provided
38
+ var customText = customSubscribeText && customUnsubscribeText
39
+
40
+ // Append '-[button-location]' to the tracking data attribute value if data-button-location is set
41
+ var optionalButtonLocation = this.$module.getAttribute('data-button-location') ? '-' + this.$module.getAttribute('data-button-location') : ''
42
+
43
+ // If response returns active, user has subscribed to notifications
44
+ if (active === true) {
45
+ this.$module.setAttribute('data-track-action', 'Unsubscribe-button' + optionalButtonLocation)
46
+
47
+ if (customText) {
48
+ this.$module.querySelector('.gem-c-single-page-notication-button__text').textContent = customUnsubscribeText
49
+ }
50
+ } else {
51
+ this.$module.setAttribute('data-track-action', 'Subscribe-button' + optionalButtonLocation)
52
+
53
+ if (customText) {
54
+ this.$module.querySelector('.gem-c-single-page-notication-button__text').textContent = customSubscribeText
55
+ }
40
56
  }
41
57
  }
42
58
  }