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

Update components to use Component #4205

Merged
merged 12 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* As govuk-frontend provides no types, TypeScript will type its exports as `any`,
* but be unable to acknowledge fields inherited from parent classes
* leading to errors when trying to assign or use them.
*
* TypeScript's shorthand ambient modules seem to also make inherited fields typed as `any`.
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#shorthand-ambient-module-declarations
*/
declare module "govuk-frontend";
8 changes: 6 additions & 2 deletions src/javascripts/application.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ createAll(Copy)
new OptionsTable()

// Initialise mobile navigation
new Navigation(document)
createAll(Navigation)

// Initialise scrollable container handling
createAll(ScrollContainer)
Expand All @@ -70,7 +70,11 @@ const lazyEmbedObserver = new IntersectionObserver(function (
) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
new EmbedCard(entry.target)
try {
new EmbedCard(entry.target)
} catch (error) {
console.log(error)
}
romaricpascal marked this conversation as resolved.
Show resolved Hide resolved
}
})
})
Expand Down
28 changes: 11 additions & 17 deletions src/javascripts/components/back-to-top.mjs
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
import { Component } from 'govuk-frontend'

/**
* Website back to top link
*/
class BackToTop {
class BackToTop extends Component {
static moduleName = 'app-back-to-top'

/**
* @param {Element} $module - HTML element
*/
constructor($module) {
if (
!($module instanceof HTMLElement) ||
!document.body.classList.contains('govuk-frontend-supported')
) {
return this
}
super($module)

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

// 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
this.$module.classList.remove('app-back-to-top--hidden')
this.$root.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 this
}
Expand All @@ -53,17 +47,17 @@ class BackToTop {

// If the subnav or the footer not visible then fix the back to top link to follow the user
if (subNavIsIntersecting || footerIsIntersecting) {
this.$module.classList.remove('app-back-to-top--fixed')
this.$root.classList.remove('app-back-to-top--fixed')
} else {
this.$module.classList.add('app-back-to-top--fixed')
this.$root.classList.add('app-back-to-top--fixed')
}

// If the subnav is visible but you can see it all at once, then a back to top link is likely not as useful.
// We hide the link but make it focusable for screen readers users who might still find it useful.
if (subNavIsIntersecting && subNavIntersectionRatio === 1) {
this.$module.classList.add('app-back-to-top--hidden')
this.$root.classList.add('app-back-to-top--hidden')
} else {
this.$module.classList.remove('app-back-to-top--hidden')
this.$root.classList.remove('app-back-to-top--hidden')
}
})

Expand Down
55 changes: 29 additions & 26 deletions src/javascripts/components/cookie-banner.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Component } from 'govuk-frontend'

import * as CookieFunctions from './cookie-functions.mjs'

const cookieBannerAcceptSelector = '.js-cookie-banner-accept'
Expand All @@ -10,26 +12,36 @@ const cookieConfirmationRejectSelector = '.js-cookie-banner-confirmation-reject'
/**
* Website cookie banner
*/
class CookieBanner {
class CookieBanner extends Component {
/**
* Check support of CookieBanner
*/
static checkSupport() {
Component.checkSupport()

if (CookieBanner.onCookiesPage()) {
throw Error('Cancelled initialisation as on cookie page')
}
}

/**
* Check if on the Cookies page
*
* @returns {boolean} Returns true if on the Cookies page
*/
static onCookiesPage() {
return window.location.pathname === '/cookies/'
}

static moduleName = 'govuk-cookie-banner'
/**
* @param {Element} $module - HTML element
*/
constructor($module) {
if (
!($module instanceof HTMLElement) ||
!document.body.classList.contains('govuk-frontend-supported') ||
// Exit if we're on the cookies page to avoid circular journeys
this.onCookiesPage()
) {
return this
}
super($module)

this.$cookieBanner = $module
this.cookieCategory =
(this.$cookieBanner.dataset &&
this.$cookieBanner.dataset.cookieCategory) ||
'analytics'
(this.$root.dataset && this.$root.dataset.cookieCategory) || 'analytics'

const $acceptButton = $module.querySelector(cookieBannerAcceptSelector)
const $rejectButton = $module.querySelector(cookieBannerRejectSelector)
Expand Down Expand Up @@ -60,7 +72,7 @@ class CookieBanner {
this.$cookieMessage = $cookieMessage
this.$cookieConfirmationAccept = $cookieConfirmationAccept
this.$cookieConfirmationReject = $cookieConfirmationReject
this.$cookieBannerHideButtons = $cookieBannerHideButtons
this.$rootHideButtons = $cookieBannerHideButtons

// Show the cookie banner to users who have not consented or have an
// outdated consent cookie
Expand All @@ -74,13 +86,13 @@ class CookieBanner {
// set previously
CookieFunctions.resetCookies()

this.$cookieBanner.removeAttribute('hidden')
this.$root.removeAttribute('hidden')
}

this.$acceptButton.addEventListener('click', () => this.acceptCookies())
this.$rejectButton.addEventListener('click', () => this.rejectCookies())

this.$cookieBannerHideButtons.forEach(($cookieBannerHideButton) => {
this.$rootHideButtons.forEach(($cookieBannerHideButton) => {
$cookieBannerHideButton.addEventListener('click', () => this.hideBanner())
})
}
Expand All @@ -89,7 +101,7 @@ class CookieBanner {
* Hide banner
*/
hideBanner() {
this.$cookieBanner.setAttribute('hidden', 'true')
this.$root.setAttribute('hidden', 'true')
}

/**
Expand Down Expand Up @@ -135,15 +147,6 @@ class CookieBanner {

confirmationMessage.focus()
}

/**
* Check if on the Cookies page
*
* @returns {boolean} Returns true if on the Cookies page
*/
onCookiesPage() {
return window.location.pathname === '/cookies/'
}
}

export default CookieBanner
17 changes: 6 additions & 11 deletions src/javascripts/components/cookies-page.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { Component } from 'govuk-frontend'

import { getConsentCookie, setConsentCookie } from './cookie-functions.mjs'

/**
* Website cookies page
*/
class CookiesPage {
class CookiesPage extends Component {
static moduleName = 'app-cookies-page'
/**
* @param {Element} $module - HTML element
*/
constructor($module) {
if (
!($module instanceof HTMLElement) ||
!document.body.classList.contains('govuk-frontend-supported')
) {
return this
}

this.$page = $module
super($module)

const $cookieForm = this.$page.querySelector('.js-cookies-page-form')
const $cookieForm = this.$root.querySelector('.js-cookies-page-form')
if (!($cookieForm instanceof HTMLFormElement)) {
return this
}
Expand All @@ -43,7 +38,7 @@ class CookiesPage {
this.$cookieFormFieldsets = $cookieFormFieldsets
this.$cookieFormButton = $cookieFormButton

const $successNotification = this.$page.querySelector(
const $successNotification = this.$root.querySelector(
'.js-cookies-page-success'
)
if ($successNotification instanceof HTMLElement) {
Expand Down
30 changes: 17 additions & 13 deletions src/javascripts/components/copy.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import ClipboardJS from 'clipboard'
import { Component } from 'govuk-frontend'

/**
* Copy button for code examples
*/
class Copy {
class Copy extends Component {
static moduleName = 'app-copy'

/**
* @param {Element} $module - HTML element
* Check if ClipboardJS is supported
*/
constructor($module) {
if (
!($module instanceof HTMLElement) ||
!document.body.classList.contains('govuk-frontend-supported') ||
!ClipboardJS.isSupported()
) {
return this
static checkSupport() {
Component.checkSupport()

if (!ClipboardJS.isSupported()) {
throw Error('ClipboardJS not supported in this browser')
}
}

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

this.$pre = this.$module.querySelector('pre')
this.$pre = this.$root.querySelector('pre')
// TODO: Throw once GOV.UK Frontend exports its errors

/** @type {number | null} */
Expand All @@ -34,8 +38,8 @@ class Copy {
this.$status.className = 'govuk-visually-hidden'
this.$status.setAttribute('aria-live', 'assertive')

this.$module.prepend(this.$status)
this.$module.prepend(this.$button)
this.$root.prepend(this.$status)
this.$root.prepend(this.$button)

const $clipboard = new ClipboardJS(this.$button, {
target: () => this.$pre
Expand Down
37 changes: 24 additions & 13 deletions src/javascripts/components/embed-card.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { Component } from 'govuk-frontend'

import { getConsentCookie } from './cookie-functions.mjs'

/**
* Embed Card Youtube functionality
*/
class EmbedCard {
class EmbedCard extends Component {
/**
* @param {Element} $module - HTML element
* Check EmbedCard support
*/
constructor($module) {
if (
!($module instanceof HTMLElement) ||
!document.body.classList.contains('govuk-frontend-supported')
) {
return this
static checkSupport() {
Component.checkSupport()

const consentCookie = getConsentCookie()

if (!consentCookie || (consentCookie && !consentCookie.campaign)) {
throw Error('Campaign consent cookies not accepted')
}
}

static moduleName = 'app-embed-card'

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

this.replacePlaceholder()
}
Expand All @@ -26,17 +36,17 @@ class EmbedCard {
* Replaces the placeholder with the iframe if cookies are set.
*/
replacePlaceholder() {
if (this.$module.querySelector('iframe')) {
if (this.$root.querySelector('iframe')) {
return
}

const consentCookie = getConsentCookie()

if (consentCookie && consentCookie.campaign) {
const placeholder = this.$module.querySelector(
const placeholder = this.$root.querySelector(
'.app-embed-card__placeholder'
)
const placeholderText = this.$module.querySelector(
const placeholderText = this.$root.querySelector(
'.app-embed-card__placeholder-text'
)

Expand All @@ -51,7 +61,7 @@ class EmbedCard {

placeholder.remove()

const iframeContainer = this.$module.querySelector(
const iframeContainer = this.$root.querySelector(
'.app-embed-card__placeholder-iframe-container'
)
iframeContainer.appendChild(iframe)
Expand All @@ -65,6 +75,7 @@ class EmbedCard {
*
* @param {string} ytId - YouTube ID
* @param {string} title - Title for iFrame (for screen readers)
* @returns {HTMLElement} - iframe element
*/
createIframe(ytId, title) {
const iframe = document.createElement('IFRAME')
Expand Down
Loading