Forum Discussion
Can a Code Block in Rise 360 be used to add Google Analytics JavaScript? If so, how?
I think the best way is manual file modification, but you are correct, if the content is frequently updated and re-published, it is step that is easily missed.
With both the code block and storyline block methods, as far as I understand, the block would need to be included on every page of the content. As the content is bookmarked (cookies), the user is potentially returned to a different page whenever they launch the content, hence the need to include the block that adds GA on every page. Once added per session, it doesn't need to be added again. Any script to add the GA script should first check to see if it already exists before adding again.
Note: If the content does not bookmark and whenever a user opens the resource it always runs from the start, you can just include the code block on the first page (provided the first page is not the cover page with menu as a code block cannot be added to that page).
This is the major advantage of adding post publish, as you manually add the script in just one location.
I think the Rise block is the simpler solution as it just cuts out any need for Storyline.
Rise Code Block Google Analytics Code
Add a Rise Code Block to the top of each page in your project and paste in the following code.
Note: Ensure you update the ln:3 of the code with you Google Analytics Measurement ID.
const MEASUREMENT_ID = "G-XXXXXXXXXX";
<script>
/** Replace with your GA4 Measurement ID (G-XXXXXXXXXX). */
const MEASUREMENT_ID = "G-XXXXXXXXXX";
const DEBUG = true;
const dbg = (...args) => {
if (DEBUG) console.debug("[add-ga]", ...args);
};
(function addGoogleAnalytics() {
dbg("start", { measurementId: MEASUREMENT_ID });
const scope = parent.parent.parent;
const win =
scope?.nodeType === 9
? scope.defaultView
: scope?.self ?? scope ?? window;
const doc =
scope?.nodeType === 9
? scope
: scope?.document ?? scope?.ownerDocument ?? document;
dbg(
"scope",
scope,
"constructor",
scope?.constructor?.name,
"doc",
doc,
"location",
doc?.location?.href ?? doc?.URL ?? "(n/a)"
);
if (!doc?.head || !win) {
console.warn("[add-ga] no document.head or window on parent.parent.parent scope");
return;
}
const idKey = MEASUREMENT_ID;
const already =
win.__ga4InjectedIds && Object.prototype.hasOwnProperty.call(win.__ga4InjectedIds, idKey);
if (already) {
dbg("skip: already marked on target window", { idKey });
return;
}
if (doc.querySelector(`script[data-ga4-inline="${MEASUREMENT_ID}"]`)) {
win.__ga4InjectedIds = win.__ga4InjectedIds || Object.create(null);
win.__ga4InjectedIds[idKey] = true;
dbg("skip: inline bootstrap already in DOM", { idKey });
return;
}
const gtagSrc = `googletagmanager.com/gtag/js`;
const existingLoader = Array.prototype.some.call(
doc.querySelectorAll(`script[src*="${gtagSrc}"]`),
(el) => {
const src = el.getAttribute("src") || "";
return src.includes(`id=${encodeURIComponent(MEASUREMENT_ID)}`) || src.includes(MEASUREMENT_ID);
}
);
if (existingLoader) {
win.__ga4InjectedIds = win.__ga4InjectedIds || Object.create(null);
win.__ga4InjectedIds[idKey] = true;
dbg("skip: gtag.js loader for this ID already present", { idKey });
return;
}
win.__ga4InjectedIds = win.__ga4InjectedIds || Object.create(null);
win.__ga4InjectedIds[idKey] = true;
const loader = doc.createElement("script");
loader.async = true;
loader.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(MEASUREMENT_ID)}`;
doc.head.appendChild(loader);
dbg("injected gtag.js loader", { src: loader.src });
const inline = doc.createElement("script");
inline.setAttribute("data-ga4-inline", MEASUREMENT_ID);
inline.textContent = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', ${JSON.stringify(MEASUREMENT_ID)});
`;
doc.head.appendChild(inline);
dbg("injected inline gtag bootstrap", { dataGa4Inline: MEASUREMENT_ID });
})();
</script>