Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
8 changes: 8 additions & 0 deletions libs/blocks/marketo/marketo.css
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,8 @@

.marketo .marketo-overlay {
position: absolute;
max-width: 600px;
margin: 0 auto;
inset: 0;
background: var(--marketo-form-overlay-background);
display: flex;
Expand Down Expand Up @@ -578,6 +580,12 @@
margin-bottom: 12px;
}

.marketo .marketo-overlay .debug-info {
font-size: 14px;
text-align: center;
color: var(--marketo-form-text);
}

@media screen and (min-width: 600px) {
.marketo .marketo-form-wrapper {
padding: var(--spacing-xxl);
Expand Down
228 changes: 139 additions & 89 deletions libs/blocks/marketo/marketo.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ import {
SLD,
MILO_EVENTS,
} from '../../utils/utils.js';
import { replaceKey } from '../../features/placeholders.js';
import { replaceKeyArray } from '../../features/placeholders.js';

const ROOT_MARGIN = 50;
const IFRAME_TIMEOUT = 3000;
const FAILURE_TIMEOUT = 10000;
export const LANA_MESSAGE = {
RENDER_FAILED: 'Marketo form did not render',
HANDSHAKE_FAILED: 'Marketo form handshake failed',
RENDER_RECOVERED: 'Marketo form rendered after timeout',
SUBMIT_FAILED: 'Marketo form submit failed',
MARKETO_FORMS_JS: 'Marketo form failed to load forms2.min.js',
};
const FORM_ID = 'form id';
const BASE_URL = 'marketo host';
const MUNCHKIN_ID = 'marketo munckin';
const FORM_STATUS = 'form.status';
const FORM_XDFRAME = 'form.xdframe';
const SUCCESS_TYPE = 'form.success.type';
const SUCCESS_CONTENT = 'form.success.content';
const SUCCESS_SECTION = 'form.success.section';
Expand All @@ -50,6 +59,30 @@ const FORM_MAP = {
};
export const FORM_PARAM = 'form';

const isVisible = (el) => !!el && (typeof el.checkVisibility === 'function'
? el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
: !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length));

export const setDataLayer = (key = '', value = '') => {
window.mcz_marketoForm_pref = window.mcz_marketoForm_pref || {};
if (!value || !key.includes('.')) return;
const keyParts = key.split('.');
const lastKey = keyParts.pop();
const formDataObject = keyParts.reduce((obj, part) => {
obj[part] = obj[part] || {};
return obj[part];
}, window.mcz_marketoForm_pref);
formDataObject[lastKey] = value;
};

export const setDataLayerObj = (formData) => {
Object.entries(formData).forEach(([key, value]) => setDataLayer(key, value));
};

export const getDataLayer = (key = '') => key
.split('.')
.reduce((obj, part) => obj?.[part], window.mcz_marketoForm_pref);

export const formValidate = (formEl) => {
formEl.classList.remove('hide-errors');
formEl.classList.add('show-warnings');
Expand Down Expand Up @@ -91,23 +124,6 @@ export const decorateURL = async (destination, baseURL = window.location) => {
return null;
};

const setPreference = (key = '', value = '') => {
window.mcz_marketoForm_pref = window.mcz_marketoForm_pref || {};
if (!value || !key.includes('.')) return;
const keyParts = key.split('.');
const lastKey = keyParts.pop();
const formDataObject = keyParts.reduce((obj, part) => {
obj[part] = obj[part] || {};
return obj[part];
}, window.mcz_marketoForm_pref);
formDataObject[lastKey] = value;
};

export const setPreferences = (formData) => {
setPreference('form.status', 'pending');
Object.entries(formData).forEach(([key, value]) => setPreference(key, value));
};

const showSuccessSection = (formData) => {
const show = async (sections) => {
sections.forEach((section) => section.classList.remove('hide-block'));
Expand Down Expand Up @@ -167,11 +183,90 @@ const hideSuccessSection = (formData) => {
);
};

export const debugTags = () => {
const len = document.cookie.length;
const tags = ['marketo'];
const signedIn = window.adobeIMS?.isSignedInUser();
const frameStatus = getDataLayer(FORM_XDFRAME);
if (getDataLayer('form.id')) tags.push(`form-${getDataLayer('form.id')}`);
if (getDataLayer('program.id')) tags.push(`program-${getDataLayer('program.id')}`);
if (getDataLayer(FORM_STATUS)) tags.push(`status-${getDataLayer(FORM_STATUS)}`);
if (len >= 8192) tags.push('cookie-8k');
else if (len >= 6144) tags.push('cookie-6k');
else if (len >= 4096) tags.push('cookie-4k');
tags.push(`ims-${signedIn ? 'signed-in' : 'signed-out'}`);
tags.push(frameStatus === 'ready' ? 'sync-ok' : 'sync-error');
if (getDataLayer('form.progressive')) tags.push('progressive');
if (getDataLayer('profile.known_visitor') === true) tags.push('known-visitor');
tags.push(`pref-lang-${getDataLayer('profile.prefLanguage') ?? ''}`);
return tags;
};

const decorateOverlay = async (el, message, callback) => {
if (el.querySelector('.marketo-overlay')) return;
const [errorRefresh, tryAgain] = await replaceKeyArray(['marketo-load-error', 'marketo-try-again'], getConfig());
const formEl = el.querySelector('form');
if (formEl) formEl.inert = true;
const searchParams = new URLSearchParams(window.location.search);
const debugMsg = searchParams.get('preview') === '1' ? message : '';
const errorMessage = createTag('p', { class: 'error', id: 'marketo-error-message' }, errorRefresh);
const formError = createTag('div', { class: 'error-container' }, errorMessage);
if (debugMsg) {
const debugInfo = createTag('p', { class: 'debug-info' });
debugInfo.textContent = debugMsg;
formError.appendChild(debugInfo);
}
const retryButton = createTag('button', { class: 'retry-button' }, tryAgain);
formError.appendChild(retryButton);
const errorOverlay = createTag(
'div',
{
class: 'marketo-overlay',
role: 'alertdialog',
'aria-modal': 'true',
'aria-labelledby': 'marketo-error-message',
},
formError,
);

retryButton.addEventListener('click', () => {
/* c8 ignore next 3 */
if (formEl) formEl.inert = false;
errorOverlay.remove();
if (callback) callback();
});

el.appendChild(errorOverlay);
};

export const logFailure = (el, msg) => {
if (el.dataset.mktoFailed) return;
const tags = debugTags();
el.dataset.mktoFailed = 'true';
window.lana?.log(msg, { tags: tags.join(','), severity: 'e', sampleRate: 100 });
decorateOverlay(el, `${msg}: ${tags.join(', ')}`, () => { window.location.reload(); });
};

export const formTimeout = (el, condition, message, timeout = FAILURE_TIMEOUT) => {
setTimeout(() => {
if (condition()) {
logFailure(el, message);
}
}, timeout);
};

const toggleSuccessSection = (formData) => {
showSuccessSection(formData);
hideSuccessSection(formData);
};

export const formSubmit = (formEl) => {
const el = formEl.closest('.marketo');
const testRecord = window.mkto_isTestRecord?.();
if (testRecord && testRecord !== 'not_test') return;
formTimeout(el, () => !el.classList.contains('success'), LANA_MESSAGE.SUBMIT_FAILED);
};

export const formSuccess = (formEl, formData) => {
const el = formEl.closest('.marketo');
const parentModal = formEl?.closest('.dialog-modal');
Expand Down Expand Up @@ -210,83 +305,33 @@ export const formSuccess = (formEl, formData) => {

if (formData?.[SUCCESS_TYPE] !== 'section') return true;
toggleSuccessSection(formData);
setPreference(SUCCESS_TYPE, 'message');
setDataLayer(SUCCESS_TYPE, 'message');
return false;
};

export const handleIframeTimeout = (el) => {
const searchParams = new URLSearchParams(window.location.search);
const config = getConfig();
const iframe = document.querySelector('iframe[src*="/index.php/form/XDFrame"]');
const formEl = el.querySelector('form');
let iframeTimeout = null;
const readyForm = (form, formData) => {
const formEl = form.getFormElem().get(0);
const el = formEl.closest('.marketo');
const isDesktop = matchMedia('(min-width: 900px)');
el.classList.remove('loading');

const handleIframeReady = (event) => {
if (event.origin !== 'https://engage.adobe.com') return;
const message = JSON.parse(event.data);
if (!message.mktoReady) return;
setPreference('form.status', 'ready');
if (iframeTimeout) clearTimeout(iframeTimeout);
const errorOverlay = el.querySelector('.marketo-overlay');
if (formEl) formEl.inert = false;
if (errorOverlay) errorOverlay.remove();
const hadFailed = el.dataset.mktoFailed === 'true';
setDataLayer(FORM_XDFRAME, 'ready');
const formVisible = isVisible(formEl);
if (hadFailed && formVisible) {
window.lana?.log(LANA_MESSAGE.RENDER_RECOVERED, { tags: 'marketo,render-recovered', severity: 'i', sampleRate: 100 });
delete el.dataset.mktoFailed;
el.querySelector('.marketo-overlay')?.remove();
formEl.inert = false;
}
window.removeEventListener('message', handleIframeReady);
};

const decorateOverlay = async () => {
const cookieSize = document.cookie.length > 4096 ? '> 4k' : '< 4k';
window.lana?.log(`Marketo iframe timeout - Cookie Size ${cookieSize}`, { tags: 'marketo', severity: 'e' });
setPreference('form.status', 'error');
if (el.querySelector('.marketo-overlay')) return;
if (!config.marketo?.showError && searchParams.get('marketoOverlay') !== 'error') return;

const marketoErrorText = await replaceKey('marketo-load-error', config);
const marketoTryAgainText = await replaceKey('marketo-try-again', config);
if (formEl) formEl.inert = true;

const errorMessage = createTag('p', { class: 'error', id: 'marketo-error-message' }, marketoErrorText);
const retryButton = createTag('button', { class: 'retry-button' }, marketoTryAgainText);
const formError = createTag('div', { class: 'error-container' }, [errorMessage, retryButton]);
const errorOverlay = createTag(
'div',
{
class: 'marketo-overlay',
role: 'alertdialog',
'aria-modal': 'true',
'aria-labelledby': 'marketo-error-message',
},
formError,
);

retryButton.addEventListener('click', () => {
const iframeSrc = iframe.src;
errorOverlay.remove();
if (formEl) formEl.inert = false;
iframe.src = '';
iframe.src = iframeSrc;
clearTimeout(iframeTimeout);
iframeTimeout = setTimeout(decorateOverlay, config.marketo?.iframeTimeout ?? IFRAME_TIMEOUT);
});

el.appendChild(errorOverlay);
};

/* c8 ignore next 4 */
if (searchParams.get('marketoOverlay') === 'error') {
decorateOverlay();
return;
}

iframeTimeout = setTimeout(decorateOverlay, config.marketo?.iframeTimeout ?? IFRAME_TIMEOUT);
window.addEventListener('message', handleIframeReady);
};

const readyForm = (form, formData) => {
const formEl = form.getFormElem().get(0);
const el = formEl.closest('.marketo');
const isDesktop = matchMedia('(min-width: 900px)');
el.classList.remove('loading');
handleIframeTimeout(el);
formTimeout(el, () => getDataLayer(FORM_XDFRAME) !== 'ready', LANA_MESSAGE.HANDSHAKE_FAILED);

formEl.addEventListener('focus', ({ target }) => {
/* c8 ignore next 9 */
Expand All @@ -301,21 +346,24 @@ const readyForm = (form, formData) => {
window.scrollTo(0, offsetPosition);
}, true);
form.onValidate(() => formValidate(formEl));
form.onSubmit(() => formSubmit(formEl));
form.onSuccess(() => formSuccess(formEl, formData));
};

export const loadMarketo = (el, formData) => {
setDataLayer(FORM_STATUS, 'loading');
const baseURL = formData[BASE_URL];
const munchkinID = formData[MUNCHKIN_ID];
const formID = formData[FORM_ID];
const { base } = getConfig();

loadScript(`${base}/deps/forms2.min.js`)
return loadScript(`${base}/deps/forms2.min.js`)
.then(() => {
const { MktoForms2 } = window;
if (!MktoForms2) throw new Error('Marketo forms not loaded');

MktoForms2.loadForm(`//${baseURL}`, munchkinID, formID);
formTimeout(el, () => !isVisible(el.querySelector('form')), LANA_MESSAGE.RENDER_FAILED);
MktoForms2.loadForm(`//${baseURL}`, munchkinID, formID, () => { setDataLayer(FORM_STATUS, 'loaded'); });
MktoForms2.whenReady((form) => { readyForm(form, formData); });

/* c8 ignore next 3 */
Expand All @@ -326,7 +374,7 @@ export const loadMarketo = (el, formData) => {
.catch(() => {
/* c8 ignore next 2 */
el.style.display = 'none';
window.lana?.log(`Error loading Marketo form for ${munchkinID}_${formID}`, { tags: 'marketo', severity: 'e' });
logFailure(el, LANA_MESSAGE.MARKETO_FORMS_JS);
});
};

Expand Down Expand Up @@ -367,6 +415,7 @@ function decorateForm(el, formData) {
}

export default async function init(el) {
setDataLayer(FORM_STATUS, 'init');
const children = Array.from(el.querySelectorAll(':scope > div'));
const encodedConfigDiv = children.shift();
const link = encodedConfigDiv.querySelector('a');
Expand Down Expand Up @@ -423,7 +472,8 @@ export default async function init(el) {
if (destinationUrl) formData[SUCCESS_CONTENT] = destinationUrl;
}

setPreferences(formData);
formData[FORM_STATUS] = 'decorated';
setDataLayerObj(formData);
decorateForm(el, formData);

loadLink(`https://${baseURL}`, { rel: 'dns-prefetch' });
Expand Down
27 changes: 27 additions & 0 deletions libs/blocks/notification/notification.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
--min-block-size-pill: 72px;
--margin-inline-pill-desktop: 80px;
--margin-inline-ribbon: 30px;
--margin-inline-ribbon-mobile: 15px;
--copy-wrap-margin-mobile: 12px;
--btn-padding-mobile: 4px 9px;
--max-inline-size-image: 75px;
--max-inline-size-icon: 231px;
--inline-size-image: auto;
Expand Down Expand Up @@ -530,6 +533,30 @@
}
}

@media screen and (max-width: 480px) {
.notification.ribbon.space-between .foreground {
margin-inline: var(--margin-inline-ribbon-mobile);
}

.notification.ribbon.space-between .foreground .icon-area {
margin-inline-end: var(--spacing-xxs);
}

.notification.ribbon.space-between .foreground .copy-wrap {
margin-inline-end: var(--copy-wrap-margin-mobile);
}

.notification.ribbon.space-between [class*="heading-"] strong {
font-size: var(--type-heading-s-size);
}

.notification.ribbon.space-between .action-area a {
font-size: var(--type-body-xxs-size);
padding: var(--btn-padding-mobile);
min-width: 0;
}
}

@media screen and (max-width: 359px) {
.notification.ribbon.inline .action-area {
flex-wrap: wrap;
Expand Down
1 change: 0 additions & 1 deletion libs/blocks/nps-csat-form/nps-csat-form.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
padding: 32px;
height: 404px;
max-width: 576px;
width: 100%;
margin: 0 auto;
}

Expand Down
Loading
Loading