Skip to content

Commit

Permalink
Header and footer analytics (#100)
Browse files Browse the repository at this point in the history
* Move SCSS includes out of global header

* Allow 100% height for pages with global headers

* Update analytics library

* Tweaks to templates

* Update footer content

* Lint

* Switch from <ul> to <menu> in header and footer

* Reduce font size of footer links

* Disable autofocus on error summary demo

* Add hidden heading for social links

* Add hover state of top links in header
  • Loading branch information
ahosgood authored Mar 8, 2024
1 parent eaef110 commit e1f4c95
Show file tree
Hide file tree
Showing 27 changed files with 352 additions and 188 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- The default pagination style is plain buttons but can be changed with the `solid` option
- Updated the HTML of the headers, adding some attributes to the HTML to reduce reliance on JavaScript
- Switched from using `<ul>` to `<menu>` in the headers and footer
- Added some attributes to the HTML of the headers to reduce reliance on the JavaScript
- Cards without images now get a top border

Expand Down
153 changes: 98 additions & 55 deletions src/nationalarchives/analytics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "./lib/analytics-helpers.mjs";
import BreadcrumbAnalytics from "./components/breadcrumbs/analytics.js";
import CheckboxesAnalytics from "./components/checkboxes/analytics.js";
import FooterAnalytics from "./components/footer/analytics.js";
import GlobalHeaderAnalytics from "./components/global-header/analytics.js";
import HeaderAnalytics from "./components/header/analytics.js";
import HeroAnalytics from "./components/hero/analytics.js";
Expand All @@ -18,6 +19,7 @@ import TextareaAnalytics from "./components/textarea/analytics.js";
const componentAnalytics = [
...BreadcrumbAnalytics,
...CheckboxesAnalytics,
...FooterAnalytics,
...GlobalHeaderAnalytics,
...HeaderAnalytics,
...HeroAnalytics,
Expand All @@ -38,7 +40,19 @@ class EventTracker {
/** @protected */
start = new Date();

constructor() {
/** @protected */
prefix = "tna";

constructor(prefix) {
if (prefix) {
this.prefix = prefix;
}
}

/**
* Initialise all TNA Frontend component analytics.
*/
initAll() {
componentAnalytics.forEach((componentConfig) => {
this.addListener(
componentConfig.scope,
Expand All @@ -65,88 +79,97 @@ class EventTracker {
return;
}
scopeArray.forEach(($scope) => {
events.forEach((componentTracking) => {
if (!componentTracking.on) {
events.forEach((eventConfig) => {
if (!eventConfig.on) {
return;
}
if (componentTracking.targetElement) {
if (eventConfig.targetElement) {
Array.from(
$scope.querySelectorAll(componentTracking.targetElement),
).forEach(($el) =>
$scope.querySelectorAll(eventConfig.targetElement),
).forEach(($el, index) =>
this.attachListener(
$el,
componentTracking.on,
$scope,
this.generateEventName(areaName, componentTracking),
componentTracking.data,
componentTracking.targetElement,
this.generateEventName(areaName, eventConfig),
eventConfig,
scope,
areaName,
index + 1,
),
);
} else {
this.attachListener(
$scope,
componentTracking.on,
$scope,
this.generateEventName(areaName, componentTracking),
componentTracking.data,
componentTracking.targetElement,
this.generateEventName(areaName, eventConfig),
eventConfig,
scope,
areaName,
1,
);
}
});
});
}

/** @protected */
generateEventName(areaName, componentTracking) {
return `${areaName}.${componentTracking.eventName || componentTracking.on}`;
generateEventName(areaName, eventConfig) {
return `${this.prefix}.${areaName}.${eventConfig.eventName || eventConfig.on}`;
}

/** @protected */
attachListener(
$el,
eventTrigger,
$scope,
eventName,
eventDataInit,
targetElement,
) {
if (!$el) {
return;
}
$el.addEventListener(eventTrigger, (event) =>
this.recordEvent(eventName, {
...eventDataInit,
value:
typeof eventDataInit.value === "function"
? eventDataInit.value.call(this, $el, $scope, event)
: eventDataInit.value || null,
state:
typeof eventDataInit.state === "function"
? eventDataInit.state.call(this, $el, $scope, event)
: eventDataInit.state || null,
group:
typeof eventDataInit.group === "function"
? eventDataInit.group.call(this, $el, $scope, event)
: eventDataInit.group || null,
scope: getXPathTo($scope),
targetElement: targetElement,
timestamp: new Date().toISOString(),
uri: window.location.pathname,
timeSincePageLoad: new Date() - this.start,
}),
attachListener($el, $scope, eventName, eventConfig, scope, areaName, index) {
const { on, data, targetElement, rootData = {} } = eventConfig;
$el.addEventListener(on, (event) =>
this.recordEvent(
eventName,
{
...data,
value: this.computedValue(data.value, $el, $scope, event, index),
state: this.computedValue(data.state, $el, $scope, event, index),
group: this.computedValue(data.group, $el, $scope, event, index),
xPath: getXPathTo($scope),
targetElement: targetElement,
// timestamp: new Date().toISOString(),
// uri: window.location.pathname,
timeSincePageLoad: new Date() - this.start,
index,
scope,
areaName,
},
Object.fromEntries(
Object.entries(rootData).map(([key, value]) => [
key,
this.computedValue(value, $el, $scope, event, index),
]),
),
),
);
}

/** @protected */
recordEvent(eventName, data) {
this.events.push({ event: eventName, "tna.data": data });
computedValue(value, $el, $scope, event, index) {
return typeof value === "function"
? value.call(this, $el, $scope, event, index)
: value || null;
}

/** @protected */
recordEvent(eventName, data, rootData = {}) {
this.events.push({
event: eventName,
[`${this.prefix}.event`]: data,
...rootData,
});
}

/** @protected */
getTnaMetaTags() {
return Object.fromEntries(
Array.from(
document.head.querySelectorAll("meta[name^='tna.'][content]"),
document.head.querySelectorAll(
`meta[name^='${this.prefix}.'][content]`,
),
).map(($metaEl) => [
$metaEl.getAttribute("name"),
$metaEl.getAttribute("content"),
Expand All @@ -167,8 +190,16 @@ class GA4 extends EventTracker {
trackingEnabled = false;
gTagId;

constructor(id) {
super();
constructor(id = null, options = {}) {
if (GA4._instance) {
return GA4._instance;
}
if (!id) {
throw Error("ID was not specified");
}
const { prefix = null, initAll = true } = options;
super(prefix);
GA4._instance = this;
this.gTagId = id;
window.dataLayer = window.dataLayer || [];
if (this.cookies.isPolicyAccepted("usage")) {
Expand All @@ -183,11 +214,23 @@ class GA4 extends EventTracker {
}
}
});
if (initAll) {
this.initAll();
}
}

/** @protected */
recordEvent(eventName, data) {
const ga4Data = { event: eventName, "tna.event": data };
recordEvent(eventName, data, rootData = {}) {
const ga4Data = {
event: eventName,
...Object.fromEntries(
Object.entries(data).map(([key, value]) => [
`${this.prefix}.event.${key}`,
value,
]),
),
...rootData,
};
this.pushToDataLayer(ga4Data);
}

Expand Down
3 changes: 2 additions & 1 deletion src/nationalarchives/components/breadcrumbs/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
{%- if params.noCollapse -%}
{%- set containerClasses = containerClasses.concat('tna-breadcrumbs--no-collapse') -%}
{%- endif -%}
<nav class="tna-breadcrumbs{{ ' ' + ( containerClasses | join(' ') ) if containerClasses.length else '' }}" data-module="tna-breadcrumbs" aria-label="Breadcrumb"{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{%- set classes = containerClasses | join(' ') -%}
<nav class="tna-breadcrumbs{% if classes %} {{ classes }}{% endif %}" data-module="tna-breadcrumbs" aria-label="Breadcrumb"{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
<ol class="tna-breadcrumbs__list" {%- if params.structuredData %} itemscope itemtype="https://schema.org/BreadcrumbList"{% endif %}>
{%- for item in params.items -%}
<li class="tna-breadcrumbs__item" {%- if params.structuredData %} itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"{% endif %}>
Expand Down
6 changes: 3 additions & 3 deletions src/nationalarchives/components/button/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"text": "Log in",
"href": "#"
},
"html": "<a href=\"#\" class=\"tna-button \">Log in</a>"
"html": "<a href=\"#\" class=\"tna-button\">Log in</a>"
},
{
"name": "with title",
Expand All @@ -16,7 +16,7 @@
"href": "#",
"title": "Log in to the service"
},
"html": "<a href=\"#\" class=\"tna-button \" title=\"Log in to the service\">Log in</a>"
"html": "<a href=\"#\" class=\"tna-button\" title=\"Log in to the service\">Log in</a>"
},
{
"name": "accent",
Expand Down Expand Up @@ -45,7 +45,7 @@
"data-testattribute": "foobar"
}
},
"html": "<a href=\"#\" class=\"tna-button \" data-testattribute=\"foobar\">Log in</a>"
"html": "<a href=\"#\" class=\"tna-button\" data-testattribute=\"foobar\">Log in</a>"
}
]
}
17 changes: 9 additions & 8 deletions src/nationalarchives/components/button/template.njk
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
{%- set buttonClasses = [params.classes] if params.classes else [] -%}
{%- set containerClasses = [params.classes] if params.classes else [] -%}
{%- if params.accent -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--accent') -%}
{%- set containerClasses = containerClasses.concat('tna-button--accent') -%}
{%- endif -%}
{%- if params.small -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--small') -%}
{%- set containerClasses = containerClasses.concat('tna-button--small') -%}
{%- endif -%}
{%- if params.plain -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--plain') -%}
{%- set containerClasses = containerClasses.concat('tna-button--plain') -%}
{%- endif -%}
{%- if params.iconOnly -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--icon-only') -%}
{%- set containerClasses = containerClasses.concat('tna-button--icon-only') -%}
{%- endif -%}
{%- if params.iconOnlyOnMobile -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--icon-only-mobile') -%}
{%- set containerClasses = containerClasses.concat('tna-button--icon-only-mobile') -%}
{%- endif -%}
{%- if params.rightAlignIcon -%}
{%- set buttonClasses = buttonClasses.concat('tna-button--icon-right') -%}
{%- set containerClasses = containerClasses.concat('tna-button--icon-right') -%}
{%- endif -%}
<{{ 'button' if params.buttonElement else 'a' }}{%- if not params.buttonElement %} href="{{ params.href }}"{%- endif %} class="tna-button {{ buttonClasses | join(' ') }}"{%- if params.buttonElement %} type="{{ params.buttonType or 'button' }}"{% endif -%}{%- if params.title %} title="{{ params.title }}"{% endif %}{% for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{%- set classes = containerClasses | join(' ') -%}
<{{ 'button' if params.buttonElement else 'a' }}{%- if not params.buttonElement %} href="{{ params.href }}"{%- endif %} class="tna-button{% if classes %} {{ classes }}{% endif %}"{%- if params.buttonElement %} type="{{ params.buttonType or 'button' }}"{% endif -%}{%- if params.title %} title="{{ params.title }}"{% endif %}{% for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{% if params.icon -%}
<i class="fa-solid fa-{{ params.icon }}" aria-hidden="true"></i>
{% endif -%}
Expand Down
Loading

0 comments on commit e1f4c95

Please sign in to comment.