Skip to content

Commit

Permalink
Add Embed Card component
Browse files Browse the repository at this point in the history
Update `application.mjs` to support multiple cookie banners on a page.

Create new Embed Card component. If require cookie not set (or script
fails to execute) placeholder image that links to YouTube video will be
created. If cookie is set then placeholder will be replaced with a YT
embed.

Add `lazyEmbedObserver` in `application.mjs`. Embed Card component will
only be initialized when the placeholder is in the viewport. This means
for a page with lots of embeds, the page the performance hit will be
limited if the user doesn't scroll the entire page.

Add tests for new Embed Card component.
  • Loading branch information
patrickpatrickpatrick committed Aug 12, 2024
1 parent b4f7de8 commit f00aa8d
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 4 deletions.
45 changes: 45 additions & 0 deletions __tests__/embed-card.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { ports } = require('../config')

const { goTo } = require('./helpers/puppeteer.js')

const cookieParam = {
name: 'design_system_cookies_policy',
value: JSON.stringify({ analytics: null, campaign: null, version: 2 }),
url: `http://localhost:${ports.preview}`
}

describe('Embed Card', () => {
beforeEach(async () => {
await page.deleteCookie({
name: cookieParam.name,
url: cookieParam.url
})

await page.setJavaScriptEnabled(true)

await goTo(page, '/community/design-system-day-2024-day-1/')
})

it('will render placeholder if cookies not accepted', async () => {
await expect(page.$('.app-embed-card__placeholder')).resolves.not.toBe(null)
})

it('will render placeholder if cookies rejected', async () => {
const buttonReject = await page.$(
'div[data-cookie-category="campaign"] .js-cookie-banner-reject'
)
await buttonReject.click()
await expect(page.$('.app-embed-card__placeholder')).resolves.not.toBe(null)
})

it('will not render placeholder if cookies accepted', async () => {
const buttonAccept = await page.$(
'div[data-cookie-category="campaign"] .js-cookie-banner-accept'
)
await buttonAccept.click()

await new Promise((resolve) => setTimeout(resolve, 3000))

await expect(page.$('.app-embed-card__placeholder')).resolves.toBe(null)
})
})
4 changes: 4 additions & 0 deletions src/images/yt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 42 additions & 4 deletions src/javascripts/application.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './components/cookie-functions.mjs'
import CookiesPage from './components/cookies-page.mjs'
import Copy from './components/copy.mjs'
import EmbedCard from './components/embed-card.mjs'
import Example from './components/example.mjs'
import Navigation from './components/navigation.mjs'
import OptionsTable from './components/options-table.mjs'
Expand All @@ -24,12 +25,10 @@ createAll(NotificationBanner)
createAll(SkipLink)

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

// Initialise analytics if consent is given
const userConsent = getConsentCookie()
Expand Down Expand Up @@ -82,3 +81,42 @@ const $cookiesPage = document.querySelector('[data-module="app-cookies-page"]')
if ($cookiesPage) {
new CookiesPage($cookiesPage)
}

const $embedCards = document.querySelectorAll('[data-module="app-embed-card"]')

const lazyEmbedObserver = new IntersectionObserver(function (
entries,
observer
) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
new EmbedCard(entry.target)
}
})
})

$embedCards.forEach(function (lazyEmbed) {
lazyEmbedObserver.observe(lazyEmbed)
})

const campaignCookieBanner = document.querySelector(
'[data-cookie-category="campaign"]'
)

if (campaignCookieBanner) {
const callback = (mutationList, observer) => {
if (mutationList.length) {
$embedCards.forEach(function (lazyEmbed) {
lazyEmbedObserver.unobserve(lazyEmbed)
lazyEmbedObserver.observe(lazyEmbed)
})
}
}

const observer = new MutationObserver(callback)
observer.observe(campaignCookieBanner, {
attributes: true,
childList: true,
subtree: true
})
}
88 changes: 88 additions & 0 deletions src/javascripts/components/embed-card.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { getConsentCookie } from './cookie-functions.mjs'

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

this.$module = $module

this.replacePlaceholder()
}

/**
* Replace placeholder
*
* Replaces the placeholder with the iframe if cookies are set.
*/
replacePlaceholder() {
if (this.$module.querySelector('iframe')) {
return
}

const consentCookie = getConsentCookie()

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

const title = placeholderText ? placeholderText.textContent : ''

const ytHref = placeholder.getAttribute('href')
const ytId = ytHref.match(
/(youtu\.be\/|youtube\.com\/(watch\?(.*&)?v=|(embed|v)\/))([^?&"'>]+)/
)[5]

const iframe = this.createIframe(ytId, title)

placeholder.remove()

const iframeContainer = this.$module.querySelector(
'.app-embed-card__placeholder-iframe-container'
)
iframeContainer.appendChild(iframe)
}
}

/**
* Create the iframe
*
* Create the iframe for YouTube embed
*
* @param {string} ytId - YouTube ID
* @param {string} title - Title for iFrame (for screen readers)
*/
createIframe(ytId, title) {
const iframe = document.createElement('IFRAME')

iframe.setAttribute('src', `https://www.youtube-nocookie.com/embed/${ytId}`)
iframe.setAttribute('width', '560')
iframe.setAttribute('height', '315')
iframe.setAttribute('title', title)
iframe.setAttribute(
'allow',
'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share;'
)
iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin')
iframe.setAttribute('allowfullscreen', 'true')
iframe.setAttribute('frameborder', '0')

return iframe
}
}

export default EmbedCard
17 changes: 17 additions & 0 deletions src/stylesheets/components/_embed-card.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.app-embed-card__placeholder-thumb-container {
position: relative;
}

.app-embed-card__placeholder-thumb-container::before {
content: "";
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
url("/images/yt.svg") 50% center / 75px no-repeat,
linear-gradient(transparent, transparent);
background-position: 50% center;
}
5 changes: 5 additions & 0 deletions src/stylesheets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ $app-code-color: #d13118;
@import "components/cookies-page";
@import "components/call-to-action";
@import "components/promo-banner";
@import "components/embed-card";
@import "components/example";
@import "components/footer";
@import "components/header";
Expand Down Expand Up @@ -429,3 +430,7 @@ pre .language-plaintext {
font-weight: normal;
}
}

.app-campaign-cookie-banner .govuk-grid-column-two-thirds {
width: 100%;
}
22 changes: 22 additions & 0 deletions views/partials/_embed-card.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% macro embedCard(params) %}
<div class="govuk-grid-row" data-module="app-embed-card">
<div class="govuk-grid-column-two-thirds-from-desktop">
{{- caller() if caller else params.content | safe -}}
<a class="app-embed-card__placeholder" href="https://www.youtube.com/watch?v={{ params.ytId }}">
<div class="app-embed-card__placeholder-thumb-container">
<img class="app-image--no-border app-embed-card__placeholder-thumb-image" alt="placeholder for {{ params.title }} youtube embed" src="https://img.youtube.com/vi/{{ params.ytId }}/hqdefault.jpg">
<span class="app-embed-card__placeholder-text govuk-visually-hidden">
{{ params.title }}
</span>
</div>
</a>
<div class="app-embed-card__placeholder-iframe-container"></div>
<p>
<a href="{{ params.transcriptHref }}">Read the full transcript</a>
</p>
</div>
<div class="govuk-grid-column-one-third-from-desktop">
<img class="app-image--no-border" src="{{ params.authorImgSrc }}" alt="{{ params.authorImgAlt }}" role="presentation">
</div>
</div>
{% endmacro %}

0 comments on commit f00aa8d

Please sign in to comment.