Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview v5: Follow GOV.UK Frontend coding standards #2961

Merged
merged 10 commits into from
Aug 1, 2023
Merged
3 changes: 2 additions & 1 deletion lib/metalsmith.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ module.exports = metalsmith
// ignore files from build
.ignore([
'.DS_Store',
'.eslintrc.js'
'.eslintrc.js',
'tsconfig.json'
])

// convert *.scss files to *.css
Expand Down
3 changes: 2 additions & 1 deletion src/javascripts/application-example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ initAll({
notificationBanner: { disableAutoFocus: true }
})

new ExamplePage(document).init()
// eslint-disable-next-line no-new
new ExamplePage(document)
28 changes: 19 additions & 9 deletions src/javascripts/application.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-new */

import { initAll } from 'govuk-frontend'

import Analytics from './components/analytics.mjs'
Expand All @@ -17,7 +19,9 @@ initAll()

// Initialise cookie banner
const $cookieBanner = document.querySelector('[data-module="govuk-cookie-banner"]')
new CookieBanner($cookieBanner).init()
if ($cookieBanner) {
new CookieBanner($cookieBanner)
}

// Initialise analytics if consent is given
const userConsent = getConsentCookie()
Expand All @@ -28,35 +32,41 @@ if (userConsent && isValidConsentCookie(userConsent) && userConsent.analytics) {
// Initialise example frames
const $examples = document.querySelectorAll('[data-module="app-example-frame"]')
$examples.forEach(($example) => {
new Example($example).init()
new Example($example)
})

// Initialise tabs
const $tabs = document.querySelectorAll('[data-module="app-tabs"]')
$tabs.forEach(($tabs) => {
new AppTabs($tabs).init()
new AppTabs($tabs)
})

// Do this after initialising tabs
new OptionsTable().init()
new OptionsTable()

// Add copy to clipboard to code blocks inside tab containers
const $codeBlocks = document.querySelectorAll('[data-module="app-copy"] pre')
$codeBlocks.forEach(($codeBlock) => {
new Copy($codeBlock).init()
new Copy($codeBlock)
})

// Initialise mobile navigation
new Navigation().init()
new Navigation(document)

// Initialise search
const $searchContainer = document.querySelector('[data-module="app-search"]')
new Search($searchContainer).init()
if ($searchContainer) {
new Search($searchContainer)
}

// Initialise back to top
const $backToTop = document.querySelector('[data-module="app-back-to-top"]')
new BackToTop($backToTop).init()
if ($backToTop) {
new BackToTop($backToTop)
}

// Initialise cookie page
const $cookiesPage = document.querySelector('[data-module="app-cookies-page"]')
new CookiesPage($cookiesPage).init()
if ($cookiesPage) {
new CookiesPage($cookiesPage)
}
2 changes: 2 additions & 0 deletions src/javascripts/components/analytics.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-nocheck

export default function loadAnalytics () {
if (!window.ga || !window.ga.loaded) {
// Load gtm script
Expand Down
18 changes: 10 additions & 8 deletions src/javascripts/components/back-to-top.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
class BackToTop {
/**
* @param {Element} $module - HTML element
*/
constructor ($module) {
this.$module = $module
}

init () {
if (!this.$module) {
return
if (!($module instanceof HTMLElement)) {
return this
}

this.$module = $module

// Check if we can use Intersection Observers
if (!('IntersectionObserver' in window)) {
// If there's no support fallback to regular behaviour
// Since JavaScript is enabled we can remove the default hidden state
return this.$module.classList.remove('app-back-to-top--hidden')
this.$module.classList.remove('app-back-to-top--hidden')
return this
}

const $footer = document.querySelector('.app-footer')
const $subNav = document.querySelector('.app-subnav')

// Check if there is anything to observe
if (!$footer || !$subNav) {
return
return this
}

let footerIsIntersecting = false
Expand Down
15 changes: 10 additions & 5 deletions src/javascripts/components/cookie-banner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ const cookieConfirmationAcceptSelector = '.js-cookie-banner-confirmation-accept'
const cookieConfirmationRejectSelector = '.js-cookie-banner-confirmation-reject'

class CookieBanner {
/**
* @param {Element} $module - HTML element
*/
constructor ($module) {
if (!($module instanceof HTMLElement)) {
return this
}

this.$module = $module
}

init () {
this.$cookieBanner = this.$module
this.$acceptButton = this.$module.querySelector(cookieBannerAcceptSelector)
this.$rejectButton = this.$module.querySelector(cookieBannerRejectSelector)
Expand Down Expand Up @@ -48,15 +53,15 @@ class CookieBanner {
}

hideBanner () {
this.$cookieBanner.setAttribute('hidden', true)
this.$cookieBanner.setAttribute('hidden', 'true')
}

acceptCookies () {
// Do actual cookie consent bit
CookieFunctions.setConsentCookie({ analytics: true })

// Hide banner and show confirmation message
this.$cookieMessage.setAttribute('hidden', true)
this.$cookieMessage.setAttribute('hidden', 'true')
this.revealConfirmationMessage(this.$cookieConfirmationAccept)
}

Expand All @@ -65,7 +70,7 @@ class CookieBanner {
CookieFunctions.setConsentCookie({ analytics: false })

// Hide banner and show confirmation message
this.$cookieMessage.setAttribute('hidden', true)
this.$cookieMessage.setAttribute('hidden', 'true')
this.revealConfirmationMessage(this.$cookieConfirmationReject)
}

Expand Down
4 changes: 3 additions & 1 deletion src/javascripts/components/cookie-functions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export function getConsentCookie () {
* @returns {boolean} True if consent cookie is valid
*/
export function isValidConsentCookie (options) {
// @ts-expect-error Property does not exist on window
return (options && options.version >= window.GDS_CONSENT_COOKIE_VERSION)
}

Expand All @@ -137,6 +138,7 @@ export function setConsentCookie (options) {
// Essential cookies cannot be deselected, ignore this cookie type
delete cookieConsent.essential

// @ts-expect-error Property does not exist on window
cookieConsent.version = window.GDS_CONSENT_COOKIE_VERSION

// Set the consent cookie
Expand Down Expand Up @@ -276,7 +278,7 @@ function setCookie (name, value, options) {
if (options.days) {
const date = new Date()
date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000))
cookieString = `${cookieString}; expires=${date.toGMTString()}`
cookieString = `${cookieString}; expires=${date.toUTCString()}`
}
if (document.location.protocol === 'https:') {
cookieString = `${cookieString}; Secure`
Expand Down
38 changes: 27 additions & 11 deletions src/javascripts/components/cookies-page.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
import { getConsentCookie, setConsentCookie } from './cookie-functions.mjs'

class CookiesPage {
/**
* @param {Element} $module - HTML element
*/
constructor ($module) {
this.$module = $module
}

init () {
this.$cookiePage = this.$module
if (!($module instanceof HTMLElement)) {
return this
}

if (!this.$cookiePage) {
return
const $cookieForm = $module.querySelector('.js-cookies-page-form')
if (!($cookieForm instanceof HTMLFormElement)) {
return this
}

this.$cookieForm = this.$cookiePage.querySelector('.js-cookies-page-form')
this.$cookieFormFieldsets = this.$cookieForm.querySelectorAll('.js-cookies-page-form-fieldset')
this.$successNotification = this.$cookiePage.querySelector('.js-cookies-page-success')
/** @satisfies {NodeListOf<HTMLFieldSetElement>} */
const $cookieFormFieldsets = $cookieForm.querySelectorAll('.js-cookies-page-form-fieldset')
const $cookieFormButton = $cookieForm.querySelector('.js-cookies-form-button')

this.$page = $module
this.$cookieForm = $cookieForm
this.$cookieFormFieldsets = $cookieFormFieldsets
this.$cookieFormButton = $cookieFormButton

const $successNotification = $module.querySelector('.js-cookies-page-success')
if ($successNotification instanceof HTMLElement) {
this.$successNotification = $successNotification
}

this.$cookieFormFieldsets.forEach(($cookieFormFieldset) => {
this.showUserPreference($cookieFormFieldset, getConsentCookie())
$cookieFormFieldset.removeAttribute('hidden')
})

// Show submit button
this.$cookieForm.querySelector('.js-cookies-form-button').removeAttribute('hidden')
this.$cookieFormButton.removeAttribute('hidden')

this.$cookieForm.addEventListener('submit', (event) => this.savePreferences(event))
}
Expand Down Expand Up @@ -70,6 +82,10 @@ class CookiesPage {
}

showSuccessNotification () {
if (!this.$successNotification) {
return
}

this.$successNotification.removeAttribute('hidden')

// Set tabindex to -1 to make the element focusable with JavaScript.
Expand Down
13 changes: 7 additions & 6 deletions src/javascripts/components/copy.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import ClipboardJS from 'clipboard'

class Copy {
/**
* @param {Element} $module - HTML element
*/
constructor ($module) {
this.$module = $module
}

init () {
if (!this.$module) {
return
if (!($module instanceof HTMLElement)) {
return this
}

this.$module = $module

const $button = document.createElement('button')
$button.className = 'app-copy-button js-copy-button'
$button.setAttribute('aria-live', 'assertive')
Expand Down
13 changes: 7 additions & 6 deletions src/javascripts/components/example-page.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
class ExamplePage {
/**
* @param {Document} $module - HTML document
*/
constructor ($module) {
this.$module = $module
}

init () {
if (!this.$module) {
return
if (!($module instanceof Document)) {
return this
}

this.$module = $module

/** @satisfies {HTMLFormElement | null} */
const $form = this.$module.querySelector('form[action="/form-handler"]')
this.preventFormSubmission($form)
Expand Down
9 changes: 3 additions & 6 deletions src/javascripts/components/example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ import iFrameResize from 'iframe-resizer/js/iframeResizer.js'
* @param {Element} $module - HTML element to use for example
*/
class Example {
/**
* @param {Element} $module - HTML element
*/
constructor ($module) {
if (!($module instanceof HTMLIFrameElement)) {
return
}

this.$module = $module
}

init () {
if (!this.$module) {
return
}

// Initialise asap for eager iframes or browsers which don't support lazy loading
if (!('loading' in this.$module) || this.$module.loading !== 'lazy') {
Expand Down
Loading