From 7c1753020d0c779fab996d74e097088e83649a78 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Thu, 9 Nov 2023 14:04:45 +0100 Subject: [PATCH 01/16] Pardot form block - callback response --- blocks/pardot-form/pardot-form.css | 24 ++ blocks/pardot-form/pardot-form.js | 399 ++++++++++++++++++++++++ blocks/pardot-form/responses/error.js | 1 + blocks/pardot-form/responses/success.js | 1 + 4 files changed, 425 insertions(+) create mode 100644 blocks/pardot-form/pardot-form.css create mode 100644 blocks/pardot-form/pardot-form.js create mode 100644 blocks/pardot-form/responses/error.js create mode 100644 blocks/pardot-form/responses/success.js diff --git a/blocks/pardot-form/pardot-form.css b/blocks/pardot-form/pardot-form.css new file mode 100644 index 00000000..12a6f38d --- /dev/null +++ b/blocks/pardot-form/pardot-form.css @@ -0,0 +1,24 @@ +.pardot-forms__floating-label-group { + margin-bottom: 35px; + margin-top: 35px; + position: relative; + } + +.pardot-forms__floating-label { + left: var(--input-padding); + pointer-events: none; + position: absolute; + top: 50%; + transform: translateY(-50%); + transition: + top var(--duration-small) var(--easing-standard), + left var(--duration-small) var(--easing-standard); + } + +.pardot-forms__floating-label-group input:focus ~ .pardot-forms__floating-label, +.pardot-forms__floating-label-group input:not(:placeholder-shown) ~ .pardot-forms__floating-label, +.pardot-forms__floating-label-group input:not(:focus):valid ~ .pardot-forms__floating-label { + bottom: 0; + left: 0; + top: 3px; + } \ No newline at end of file diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js new file mode 100644 index 00000000..653720c9 --- /dev/null +++ b/blocks/pardot-form/pardot-form.js @@ -0,0 +1,399 @@ +import { loadScript, sampleRUM } from '../../scripts/lib-franklin.js'; + +const thankyouMessage = `

Thank you

+

Your information has been submitted. Someone will be in touch with you shortly.

+`; + +const errorMessage = `

There's an error

+

Your information has not been submitted successfuly. Please try again later.

+`; + +// Form Block identifies the submit endpoint via these rules and in order +// 1. action property on the submit button +// 2. SUBMIT_ACTION constant +// 3. the path of the spreadsheet +const SUBMIT_ACTION = ''; + +//callback +window.logResult= function(json) { + debugger; + if(json.result === "success"){ + submissionSuccess(); + } else if(json.result === "error") { + submissionFailure(); + } +}; + +function generateUnique() { + return new Date().valueOf() + Math.random(); +} + +function constructPayload(form) { + const payload = { __id__: generateUnique() }; + [...form.elements].forEach((fe) => { + if (fe.name) { + if (fe.type === 'radio') { + if (fe.checked) payload[fe.name] = fe.value; + } else if (fe.type === 'checkbox') { + if (fe.checked) payload[fe.name] = payload[fe.name] ? `${payload[fe.name]},${fe.value}` : fe.value; + } else if (fe.type !== 'file') { + payload[fe.name] = fe.value; + } + } + }); + payload['callback'] = 'logResult'; + return { payload }; +} + +async function submissionSuccess(error, form) { + sampleRUM('form:submit'); + const thankyouDiv = document.createElement('div'); + thankyouDiv.innerHTML = thankyouMessage; + form.replaceWith(thankyouDiv); +} + +async function submissionFailure(error, form) { + const errorDiv = document.createElement('div'); + errorDiv.innerHTML = errorMessage; + form.replaceWith(errorDiv); + form.setAttribute('data-submitting', 'false'); + form.querySelector('button[type="submit"]').disabled = false; +} + +async function prepareRequest(form) { + const { payload } = constructPayload(form); + const body = JSON.stringify({ data: payload }); + const url = form.dataset.action; + + var serializedData = serialize(payload); + loadScript(url + '?' + serializedData, { type: 'text/javascript', charset: 'UTF-8'}); +} + +function serialize(obj){ + let str = []; + for(let p in obj){ + if(obj.hasOwnProperty(p)){ + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); + } + } + return str.join('&'); +} + +async function handleSubmit(form) { + if (form.getAttribute('data-submitting') !== 'true') { + form.setAttribute('data-submitting', 'true'); + await prepareRequest(form); + } +} + +function setPlaceholder(element, fd) { + if (fd.Placeholder) { + element.setAttribute('placeholder', fd.Placeholder); + } +} + +const constraintsDef = Object.entries({ + 'email|text': [['Max', 'maxlength'], ['Min', 'minlength']], + 'number|range|date': ['Max', 'Min', 'Step'], + file: ['Accept', 'Multiple'], + fieldset: ['Max', 'Min'], +}).flatMap(([types, constraintDef]) => types.split('|') + .map((type) => [type, constraintDef.map((cd) => (Array.isArray(cd) ? cd : [cd, cd]))])); + +const constraintsObject = Object.fromEntries(constraintsDef); + +function setConstraints(element, fd) { + const constraints = constraintsObject[fd.Type]; + if (constraints) { + constraints + .filter(([nm]) => fd[nm]) + .forEach(([nm, htmlNm]) => { + element.setAttribute(htmlNm, fd[nm]); + }); + } +} + +function createLabel(fd, tagName = 'label') { + const label = document.createElement(tagName); + label.setAttribute('for', fd.Id); + label.className = 'field-label'; + label.textContent = fd.Label || ''; + if (fd.Tooltip) { + label.title = fd.Tooltip; + } + return label; +} + +function createHelpText(fd) { + const div = document.createElement('div'); + div.className = 'field-description'; + div.setAttribute('aria-live', 'polite'); + div.innerText = fd.Description; + div.id = `${fd.Id}-description`; + return div; +} + +function createFieldWrapper(fd, tagName = 'div') { + const fieldWrapper = document.createElement(tagName); + const nameStyle = fd.Name ? ` form-${fd.Name}` : ''; + const fieldId = `form-${fd.Type}-wrapper${nameStyle}`; + fieldWrapper.className = fieldId; + if (fd.Fieldset) { + fieldWrapper.dataset.fieldset = fd.Fieldset; + } + if (fd.Mandatory.toLowerCase() === 'true') { + fieldWrapper.dataset.required = ''; + } + fieldWrapper.classList.add('field-wrapper'); + fieldWrapper.append(createLabel(fd)); + return fieldWrapper; +} + +function createButton(fd) { + const wrapper = createFieldWrapper(fd); + const button = document.createElement('button'); + button.textContent = fd.Label; + button.type = fd.Type; + if (button.type === 'submit' && fd.Action) { + button.formAction = fd.Action; + } + button.classList.add('button'); + button.dataset.redirect = fd.Extra || ''; + button.id = fd.Id; + button.name = fd.Name; + wrapper.replaceChildren(button); + return wrapper; +} +function createSubmit(fd) { + const wrapper = createButton(fd); + return wrapper; +} + +function createInput(fd) { + const input = document.createElement('input'); + input.type = fd.Type; + setPlaceholder(input, fd); + setConstraints(input, fd); + return input; +} + +const withFieldWrapper = (element) => (fd) => { + const wrapper = createFieldWrapper(fd); + wrapper.append(element(fd)); + return wrapper; +}; + +const createTextArea = withFieldWrapper((fd) => { + const input = document.createElement('textarea'); + setPlaceholder(input, fd); + return input; +}); + +const createSelect = withFieldWrapper((fd) => { + const select = document.createElement('select'); + if (fd.Placeholder) { + const ph = document.createElement('option'); + ph.textContent = fd.Placeholder; + ph.setAttribute('selected', ''); + ph.setAttribute('disabled', ''); + select.append(ph); + } + fd.Options.split(',').forEach((o) => { + const option = document.createElement('option'); + option.textContent = o.trim(); + option.value = o.trim(); + select.append(option); + }); + return select; +}); + +function createRadio(fd) { + const wrapper = createFieldWrapper(fd); + wrapper.insertAdjacentElement('afterbegin', createInput(fd)); + return wrapper; +} + +const createOutput = withFieldWrapper((fd) => { + const output = document.createElement('output'); + output.name = fd.Name; + output.dataset.fieldset = fd.Fieldset ? fd.Fieldset : ''; + output.innerText = fd.Value; + return output; +}); + +function createHidden(fd) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.id = fd.Id; + input.name = fd.Name; + input.value = fd.Value; + return input; +} + +function createLegend(fd) { + return createLabel(fd, 'legend'); +} + +function createFieldSet(fd) { + const wrapper = createFieldWrapper(fd, 'fieldset'); + wrapper.name = fd.Name; + wrapper.replaceChildren(createLegend(fd)); + if (fd.Repeatable && fd.Repeatable.toLowerCase() === 'true') { + setConstraints(wrapper, fd); + wrapper.dataset.repeatable = 'true'; + } + return wrapper; +} + +function groupFieldsByFieldSet(form) { + const fieldsets = form.querySelectorAll('fieldset'); + fieldsets?.forEach((fieldset) => { + const fields = form.querySelectorAll(`[data-fieldset="${fieldset.name}"`); + fields?.forEach((field) => { + fieldset.append(field); + }); + }); +} + +function createPlainText(fd) { + const paragraph = document.createElement('p'); + const nameStyle = fd.Name ? `form-${fd.Name}` : ''; + paragraph.className = nameStyle; + paragraph.dataset.fieldset = fd.Fieldset ? fd.Fieldset : ''; + paragraph.textContent = fd.Label; + return paragraph; +} + +const getId = (function getId() { + const ids = {}; + return (name) => { + ids[name] = ids[name] || 0; + const idSuffix = ids[name] ? `-${ids[name]}` : ''; + ids[name] += 1; + return `${name}${idSuffix}`; + }; +}()); + +const fieldRenderers = { + radio: createRadio, + checkbox: createRadio, + textarea: createTextArea, + select: createSelect, + button: createButton, + submit: createSubmit, + output: createOutput, + hidden: createHidden, + fieldset: createFieldSet, + plaintext: createPlainText, +}; + +function renderField(fd) { + const renderer = fieldRenderers[fd.Type]; + let field; + if (typeof renderer === 'function') { + field = renderer(fd); + } else { + field = createFieldWrapper(fd); + field.append(createInput(fd)); + } + if (fd.Description) { + field.append(createHelpText(fd)); + } + return field; +} + +async function fetchData(url) { + const resp = await fetch(url); + const json = await resp.json(); + return json.data.map((fd) => ({ + ...fd, + Id: fd.Id || getId(fd.Name), + Value: fd.Value || '', + })); +} + +async function fetchForm(pathname) { + // get the main form + const jsonData = await fetchData(pathname); + return jsonData; +} + +function showError(evnt) { + const field = evnt.target; + const fieldWrapper = field.parentNode; + fieldWrapper.classList.add('invalid'); + let errorSpan = fieldWrapper.querySelector('span.error'); + if (!errorSpan) { + errorSpan = document.createElement('span'); + errorSpan.classList.add('error'); + fieldWrapper.append(errorSpan); + } + errorSpan.innerText = field.validationMessage; + // eslint-disable-next-line no-use-before-define + field.addEventListener('blur', hideError); +} + +function hideError(evnt) { + const field = evnt.target; + const fieldWrapper = field.parentNode; + // to avoid showing error messages on blur + if (field.checkValidity()) { + fieldWrapper.classList.remove('invalid'); + } else { + fieldWrapper.classList.add('invalid'); + } +} + +function decorateValidation(form) { + form.setAttribute('novalidate', ''); + form.querySelectorAll('input,textarea,select').forEach((el) => { + el.addEventListener('invalid', showError); + }); +} + + +async function createForm(formURL) { + const { pathname } = new URL(formURL); + const data = await fetchForm(pathname); + const form = document.createElement('form'); + data.forEach((fd) => { + const el = renderField(fd); + const input = el.querySelector('input,textarea,select'); + if (fd.Mandatory && fd.Mandatory.toLowerCase() === 'true') { + input.setAttribute('required', 'required'); + } + if (input) { + input.id = fd.Id; + input.name = fd.Name; + input.value = fd.Value; + if (fd.Description) { + input.setAttribute('aria-describedby', `${fd.Id}-description`); + } + } + form.append(el); + }); + groupFieldsByFieldSet(form); + // eslint-disable-next-line prefer-destructuring + form.addEventListener('submit', (e) => { + let isValid = true; + if (form.hasAttribute('novalidate')) { + isValid = form.checkValidity(); + } + e.preventDefault(); + if (isValid) { + e.submitter.setAttribute('disabled', ''); + form.dataset.action = e.submitter.formAction || SUBMIT_ACTION || pathname.split('.json')[0]; + handleSubmit(form); + } + }); + decorateValidation(form); + return form; +} + +export default async function decorate(block) { + const formLink = block.querySelector('a[href$=".json"]'); + if (formLink) { + const form = await createForm(formLink.href); + formLink.replaceWith(form); + } +} diff --git a/blocks/pardot-form/responses/error.js b/blocks/pardot-form/responses/error.js new file mode 100644 index 00000000..b6497cd6 --- /dev/null +++ b/blocks/pardot-form/responses/error.js @@ -0,0 +1 @@ +logResult({ 'result' : 'error' }); \ No newline at end of file diff --git a/blocks/pardot-form/responses/success.js b/blocks/pardot-form/responses/success.js new file mode 100644 index 00000000..b397f668 --- /dev/null +++ b/blocks/pardot-form/responses/success.js @@ -0,0 +1 @@ +logResult({ 'result' : 'success' }); \ No newline at end of file From 8f03640ceff4af0b3d306463454fb8357566e012 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Tue, 14 Nov 2023 11:57:40 +0100 Subject: [PATCH 02/16] Update messages --- blocks/pardot-form/pardot-form.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index 653720c9..ff8f5925 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -1,7 +1,7 @@ import { loadScript, sampleRUM } from '../../scripts/lib-franklin.js'; const thankyouMessage = `

Thank you

-

Your information has been submitted. Someone will be in touch with you shortly.

+

Your information has been submitted.

`; const errorMessage = `

There's an error

@@ -45,19 +45,21 @@ function constructPayload(form) { return { payload }; } -async function submissionSuccess(error, form) { +async function submissionSuccess() { sampleRUM('form:submit'); const thankyouDiv = document.createElement('div'); thankyouDiv.innerHTML = thankyouMessage; + const form = document.querySelector('form'); form.replaceWith(thankyouDiv); } -async function submissionFailure(error, form) { +async function submissionFailure() { const errorDiv = document.createElement('div'); errorDiv.innerHTML = errorMessage; - form.replaceWith(errorDiv); + const form = document.querySelector('form'); form.setAttribute('data-submitting', 'false'); form.querySelector('button[type="submit"]').disabled = false; + form.replaceWith(errorDiv); } async function prepareRequest(form) { From a744562c94b55df620de3bb72f0458b2db375fe7 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 15 Nov 2023 09:56:22 +0100 Subject: [PATCH 03/16] Updated with placeholders Thank you & Error messages --- blocks/pardot-form/pardot-form.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index ff8f5925..ef09c91c 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -1,11 +1,12 @@ import { loadScript, sampleRUM } from '../../scripts/lib-franklin.js'; +import { getTextLabel } from '../../scripts/common.js'; -const thankyouMessage = `

Thank you

-

Your information has been submitted.

+const thankyouMessage = `

${getTextLabel('Successful submission title')}

+

${getTextLabel('Successful submission text')}

`; -const errorMessage = `

There's an error

-

Your information has not been submitted successfuly. Please try again later.

+const errorMessage = `

${getTextLabel('Error submission title')}

+

${getTextLabel('Error submission text')}

`; // Form Block identifies the submit endpoint via these rules and in order From d64a7194d24cbc788a23578d8d324d4a02bb4470 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 15 Nov 2023 10:50:35 +0100 Subject: [PATCH 04/16] Remove debuggers --- blocks/pardot-form/pardot-form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index ef09c91c..b644d01d 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -17,7 +17,6 @@ const SUBMIT_ACTION = ''; //callback window.logResult= function(json) { - debugger; if(json.result === "success"){ submissionSuccess(); } else if(json.result === "error") { From d8c1ce3d66de77e8d9d4166ba4ec9ee96b333cf8 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 15 Nov 2023 16:03:12 +0100 Subject: [PATCH 05/16] Fix linting issues --- blocks/pardot-form/pardot-form.js | 70 ++++++++++++++----------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index b644d01d..1b97fa6f 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -15,13 +15,34 @@ const errorMessage = `

${getTextLabel('Error // 3. the path of the spreadsheet const SUBMIT_ACTION = ''; -//callback -window.logResult= function(json) { - if(json.result === "success"){ - submissionSuccess(); - } else if(json.result === "error") { - submissionFailure(); - } +async function submissionSuccess() { + sampleRUM('form:submit'); + const thankyouDiv = document.createElement('div'); + thankyouDiv.innerHTML = thankyouMessage; + const form = document.querySelector('form'); + form.replaceWith(thankyouDiv); +} + +async function submissionFailure() { + const errorDiv = document.createElement('div'); + errorDiv.innerHTML = errorMessage; + const form = document.querySelector('form'); + form.setAttribute('data-submitting', 'false'); + form.querySelector('button[type="submit"]').disabled = false; + form.replaceWith(errorDiv); +} + +function serialize(obj) { + const str = Object.keys(obj).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`); + return str.join('&'); +} +// eslint-disable-next-line func-names +window.logResult = function (json) { + if (json.result === 'success') { + submissionSuccess(); + } else if (json.result === 'error') { + submissionFailure(); + } }; function generateUnique() { @@ -41,44 +62,16 @@ function constructPayload(form) { } } }); - payload['callback'] = 'logResult'; + payload.callback = 'logResult'; return { payload }; } -async function submissionSuccess() { - sampleRUM('form:submit'); - const thankyouDiv = document.createElement('div'); - thankyouDiv.innerHTML = thankyouMessage; - const form = document.querySelector('form'); - form.replaceWith(thankyouDiv); -} - -async function submissionFailure() { - const errorDiv = document.createElement('div'); - errorDiv.innerHTML = errorMessage; - const form = document.querySelector('form'); - form.setAttribute('data-submitting', 'false'); - form.querySelector('button[type="submit"]').disabled = false; - form.replaceWith(errorDiv); -} - async function prepareRequest(form) { const { payload } = constructPayload(form); - const body = JSON.stringify({ data: payload }); const url = form.dataset.action; - var serializedData = serialize(payload); - loadScript(url + '?' + serializedData, { type: 'text/javascript', charset: 'UTF-8'}); -} - -function serialize(obj){ - let str = []; - for(let p in obj){ - if(obj.hasOwnProperty(p)){ - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); - } - } - return str.join('&'); + const serializedData = serialize(payload); + loadScript(`${url}?${serializedData}`, { type: 'text/javascript', charset: 'UTF-8' }); } async function handleSubmit(form) { @@ -353,7 +346,6 @@ function decorateValidation(form) { }); } - async function createForm(formURL) { const { pathname } = new URL(formURL); const data = await fetchForm(pathname); From 463b13fcd5b52df120b4fb19e101e9ed61bb2bf6 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 15 Nov 2023 16:05:50 +0100 Subject: [PATCH 06/16] More linting fixes --- blocks/pardot-form/responses/error.js | 3 ++- blocks/pardot-form/responses/success.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/blocks/pardot-form/responses/error.js b/blocks/pardot-form/responses/error.js index b6497cd6..b16cf5c6 100644 --- a/blocks/pardot-form/responses/error.js +++ b/blocks/pardot-form/responses/error.js @@ -1 +1,2 @@ -logResult({ 'result' : 'error' }); \ No newline at end of file +// eslint-disable-next-line no-undef +logResult({ result: 'error' }); diff --git a/blocks/pardot-form/responses/success.js b/blocks/pardot-form/responses/success.js index b397f668..372e7d71 100644 --- a/blocks/pardot-form/responses/success.js +++ b/blocks/pardot-form/responses/success.js @@ -1 +1,2 @@ -logResult({ 'result' : 'success' }); \ No newline at end of file +// eslint-disable-next-line no-undef +logResult({ result: 'success' }); From 81e6da4d466bfccf25463c3950b37fd36f6b9da5 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Thu, 16 Nov 2023 14:24:49 +0100 Subject: [PATCH 07/16] Styling update Pardot Form block --- blocks/pardot-form/pardot-form.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/blocks/pardot-form/pardot-form.css b/blocks/pardot-form/pardot-form.css index 12a6f38d..660283cb 100644 --- a/blocks/pardot-form/pardot-form.css +++ b/blocks/pardot-form/pardot-form.css @@ -1,10 +1,10 @@ -.pardot-forms__floating-label-group { +.pardot-form__floating-label-group { margin-bottom: 35px; margin-top: 35px; position: relative; } -.pardot-forms__floating-label { +.pardot-form__floating-label { left: var(--input-padding); pointer-events: none; position: absolute; @@ -15,9 +15,9 @@ left var(--duration-small) var(--easing-standard); } -.pardot-forms__floating-label-group input:focus ~ .pardot-forms__floating-label, -.pardot-forms__floating-label-group input:not(:placeholder-shown) ~ .pardot-forms__floating-label, -.pardot-forms__floating-label-group input:not(:focus):valid ~ .pardot-forms__floating-label { +.pardot-form__floating-label-group input:focus ~ .pardot-form__floating-label, +.pardot-form__floating-label-group input:not(:placeholder-shown) ~ .pardot-form__floating-label, +.pardot-form__floating-label-group input:not(:focus):valid ~ .pardot-form__floating-label { bottom: 0; left: 0; top: 3px; From e1ea5fad335884a6bdc2c1232e16610046494d40 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Thu, 16 Nov 2023 14:36:00 +0100 Subject: [PATCH 08/16] Integrate newsletter form with form handler --- blocks/v2-forms/responses/error.js | 2 ++ blocks/v2-forms/responses/success.js | 2 ++ blocks/v2-forms/v2-forms.js | 52 +++++++++++++++++++++++++++ blocks/v2-newsletter/v2-newsletter.js | 37 ++++++++++++++++--- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 blocks/v2-forms/responses/error.js create mode 100644 blocks/v2-forms/responses/success.js diff --git a/blocks/v2-forms/responses/error.js b/blocks/v2-forms/responses/error.js new file mode 100644 index 00000000..cd9785c8 --- /dev/null +++ b/blocks/v2-forms/responses/error.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +logResponse({ result: 'error' }); diff --git a/blocks/v2-forms/responses/success.js b/blocks/v2-forms/responses/success.js new file mode 100644 index 00000000..1fdf3a58 --- /dev/null +++ b/blocks/v2-forms/responses/success.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-undef +logResponse({ result: 'success' }); diff --git a/blocks/v2-forms/v2-forms.js b/blocks/v2-forms/v2-forms.js index ec869470..a5cfbc56 100644 --- a/blocks/v2-forms/v2-forms.js +++ b/blocks/v2-forms/v2-forms.js @@ -1,3 +1,4 @@ +import { loadScript } from '../../scripts/lib-franklin.js'; import { createElement } from '../../scripts/common.js'; // cache contains the form element that should be reused @@ -5,6 +6,43 @@ const formCache = new Map(); const blockName = 'v2-forms'; +function serialize(obj) { + const str = Object.keys(obj).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`); + return str.join('&'); +} + +function constructPayload(form) { + const payload = {}; + [...form.elements].forEach((fe) => { + if (fe.name) { + if (fe.type === 'radio') { + if (fe.checked) payload[fe.name] = fe.value; + } else if (fe.type === 'checkbox') { + if (fe.checked) payload[fe.name] = payload[fe.name] ? `${payload[fe.name]},${fe.value}` : fe.value; + } else if (fe.type !== 'file') { + payload[fe.name] = fe.value; + } + } + }); + payload.callback = 'logResponse'; + return { payload }; +} + +async function prepareRequest(form) { + const { payload } = constructPayload(form); + const url = form.dataset.action; + + const serializedData = serialize(payload); + loadScript(`${url}?${serializedData}`, { type: 'text/javascript', charset: 'UTF-8' }); +} + +async function handleSubmit(form) { + if (form.getAttribute('data-submitting') !== 'true') { + form.setAttribute('data-submitting', 'true'); + await prepareRequest(form); + } +} + const addForm = async (block) => { const displayValue = block.style.display; block.style.display = 'none'; @@ -41,6 +79,20 @@ const addForm = async (block) => { formCache.set(formName, formWrapper); block.style.display = displayValue; + + // eslint-disable-next-line prefer-destructuring + form.addEventListener('submit', (e) => { + let isValid = true; + if (form.hasAttribute('novalidate')) { + isValid = form.checkValidity(); + } + e.preventDefault(); + if (isValid) { + e.submitter.setAttribute('disabled', ''); + form.dataset.action = e.submitter.formAction; + handleSubmit(form); + } + }); }; export default async function decorate(block) { diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index 533a327f..1ada5694 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -1,12 +1,41 @@ import { - loadBlock, + loadBlock, sampleRUM, } from '../../scripts/lib-franklin.js'; -import { - createElement, -} from '../../scripts/common.js'; +import { getTextLabel, createElement } from '../../scripts/common.js'; const blockName = 'v2-newsletter'; +const thankyouMessage = `

${getTextLabel('Successful submission title')}

+

${getTextLabel('Successful submission text')}

`; + +const errorMessage = `

${getTextLabel('Error submission title')}

+

${getTextLabel('Error submission text')}

`; + +async function submissionSuccess() { + sampleRUM('form:submit'); + const thankyouDiv = document.createElement('div'); + thankyouDiv.innerHTML = thankyouMessage; + const form = document.querySelector('form'); + form.replaceWith(thankyouDiv); +} + +async function submissionFailure() { + const errorDiv = document.createElement('div'); + errorDiv.innerHTML = errorMessage; + const form = document.querySelector('form'); + form.setAttribute('data-submitting', 'false'); + form.querySelector('button[type="submit"]').disabled = false; + form.replaceWith(errorDiv); +} + +// eslint-disable-next-line func-names +window.logResponse = function (json) { + if (json.result === 'success') { + submissionSuccess(); + } else if (json.result === 'error') { + submissionFailure(); + } +}; export default async function decorate(block) { const formLimk = block.firstElementChild.innerText.trim(); const html = block.firstElementChild.nextElementSibling.firstElementChild.innerHTML; From 746492e02ae767d1b2c821313ee604388ae9da09 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Thu, 16 Nov 2023 15:21:12 +0100 Subject: [PATCH 09/16] Getting response from form handler --- blocks/v2-forms/responses/error.js | 2 +- blocks/v2-forms/responses/success.js | 2 +- blocks/v2-forms/v2-forms.js | 11 ++++++----- blocks/v2-newsletter/v2-newsletter.js | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/blocks/v2-forms/responses/error.js b/blocks/v2-forms/responses/error.js index cd9785c8..b16cf5c6 100644 --- a/blocks/v2-forms/responses/error.js +++ b/blocks/v2-forms/responses/error.js @@ -1,2 +1,2 @@ // eslint-disable-next-line no-undef -logResponse({ result: 'error' }); +logResult({ result: 'error' }); diff --git a/blocks/v2-forms/responses/success.js b/blocks/v2-forms/responses/success.js index 1fdf3a58..372e7d71 100644 --- a/blocks/v2-forms/responses/success.js +++ b/blocks/v2-forms/responses/success.js @@ -1,2 +1,2 @@ // eslint-disable-next-line no-undef -logResponse({ result: 'success' }); +logResult({ result: 'success' }); diff --git a/blocks/v2-forms/v2-forms.js b/blocks/v2-forms/v2-forms.js index a5cfbc56..fbf1eafc 100644 --- a/blocks/v2-forms/v2-forms.js +++ b/blocks/v2-forms/v2-forms.js @@ -80,17 +80,18 @@ const addForm = async (block) => { block.style.display = displayValue; + const formObj = document.querySelector('form'); // eslint-disable-next-line prefer-destructuring - form.addEventListener('submit', (e) => { + formObj.addEventListener('submit', (e) => { let isValid = true; - if (form.hasAttribute('novalidate')) { - isValid = form.checkValidity(); + if (formObj.hasAttribute('novalidate')) { + isValid = formObj.checkValidity(); } e.preventDefault(); if (isValid) { e.submitter.setAttribute('disabled', ''); - form.dataset.action = e.submitter.formAction; - handleSubmit(form); + formObj.dataset.action = e.submitter.formAction; + handleSubmit(formObj); } }); }; diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index 1ada5694..a4f92700 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -29,7 +29,7 @@ async function submissionFailure() { } // eslint-disable-next-line func-names -window.logResponse = function (json) { +window.logResult = function (json) { if (json.result === 'success') { submissionSuccess(); } else if (json.result === 'error') { From 29af0efccd85e0c7002d40608a3af6d80b05c0d8 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Fri, 17 Nov 2023 10:27:39 +0100 Subject: [PATCH 10/16] Fix submission action and form handler response --- blocks/v2-forms/v2-forms.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/blocks/v2-forms/v2-forms.js b/blocks/v2-forms/v2-forms.js index fbf1eafc..913b5688 100644 --- a/blocks/v2-forms/v2-forms.js +++ b/blocks/v2-forms/v2-forms.js @@ -24,7 +24,7 @@ function constructPayload(form) { } } }); - payload.callback = 'logResponse'; + payload.callback = 'logResult'; return { payload }; } @@ -59,10 +59,6 @@ const addForm = async (block) => { action="${formAction}" >${formContent.default} -
- - -
`; @@ -90,7 +86,7 @@ const addForm = async (block) => { e.preventDefault(); if (isValid) { e.submitter.setAttribute('disabled', ''); - formObj.dataset.action = e.submitter.formAction; + formObj.dataset.action = e.currentTarget.action; handleSubmit(formObj); } }); From 75db3dd1220c69d6eeedfc6fdba014e18e5cd003 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Tue, 21 Nov 2023 11:53:03 +0100 Subject: [PATCH 11/16] Improvements on form selector specificity and response messages --- blocks/pardot-form/pardot-form.js | 19 ++++++++------- blocks/v2-newsletter/v2-newsletter.js | 35 ++++++++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index 1b97fa6f..5e48b3a9 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -1,12 +1,12 @@ import { loadScript, sampleRUM } from '../../scripts/lib-franklin.js'; import { getTextLabel } from '../../scripts/common.js'; -const thankyouMessage = `

${getTextLabel('Successful submission title')}

-

${getTextLabel('Successful submission text')}

+const successMessage = `

${getTextLabel('Successful submission title')}

+

${getTextLabel('Successful submission text')}

`; -const errorMessage = `

${getTextLabel('Error submission title')}

-

${getTextLabel('Error submission text')}

+const errorMessage = `

${getTextLabel('Error submission title')}

+

${getTextLabel('Error submission text')}

`; // Form Block identifies the submit endpoint via these rules and in order @@ -17,16 +17,17 @@ const SUBMIT_ACTION = ''; async function submissionSuccess() { sampleRUM('form:submit'); - const thankyouDiv = document.createElement('div'); - thankyouDiv.innerHTML = thankyouMessage; - const form = document.querySelector('form'); - form.replaceWith(thankyouDiv); + const successDiv = document.createElement('div'); + successDiv.innerHTML = successMessage; + const form = document.querySelector('form[data-submitting=true]'); + form.setAttribute('data-submitting', 'false'); + form.replaceWith(successDiv); } async function submissionFailure() { const errorDiv = document.createElement('div'); errorDiv.innerHTML = errorMessage; - const form = document.querySelector('form'); + const form = document.querySelector('form[data-submitting=true]'); form.setAttribute('data-submitting', 'false'); form.querySelector('button[type="submit"]').disabled = false; form.replaceWith(errorDiv); diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index a4f92700..622fc01b 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -5,28 +5,34 @@ import { getTextLabel, createElement } from '../../scripts/common.js'; const blockName = 'v2-newsletter'; -const thankyouMessage = `

${getTextLabel('Successful submission title')}

-

${getTextLabel('Successful submission text')}

`; - -const errorMessage = `

${getTextLabel('Error submission title')}

-

${getTextLabel('Error submission text')}

`; +//* init response handling * +const successTitle = `${getTextLabel('Successful submission title')}`; +const successText = `${getTextLabel('Successful submission text')}`; async function submissionSuccess() { sampleRUM('form:submit'); - const thankyouDiv = document.createElement('div'); - thankyouDiv.innerHTML = thankyouMessage; - const form = document.querySelector('form'); - form.replaceWith(thankyouDiv); + const form = document.querySelector('form[data-submitting=true]'); + form.setAttribute('data-submitting', 'false'); + const title = document.querySelector(`.${blockName}__title`); + const message = document.createElement('p'); + message.textContent = successText; + title.textContent = successTitle; + form.replaceWith(message); } +const errorTitle = `${getTextLabel('Error submission title')}`; +const errorText = `${getTextLabel('Error submission text')}`; + async function submissionFailure() { - const errorDiv = document.createElement('div'); - errorDiv.innerHTML = errorMessage; - const form = document.querySelector('form'); + const form = document.querySelector('form[data-submitting=true]'); form.setAttribute('data-submitting', 'false'); - form.querySelector('button[type="submit"]').disabled = false; - form.replaceWith(errorDiv); + const title = document.querySelector(`.${blockName}__title`); + const message = document.createElement('p'); + message.textContent = errorText; + title.textContent = errorTitle; + form.replaceWith(message); } +//* end response handling * // eslint-disable-next-line func-names window.logResult = function (json) { @@ -36,6 +42,7 @@ window.logResult = function (json) { submissionFailure(); } }; + export default async function decorate(block) { const formLimk = block.firstElementChild.innerText.trim(); const html = block.firstElementChild.nextElementSibling.firstElementChild.innerHTML; From 2d2cc1eceae4fef0478bfe84aeaca591a36a4ac4 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Tue, 21 Nov 2023 12:25:51 +0100 Subject: [PATCH 12/16] Updated msg placeholder.json keys based on newsletter specific feedback message --- blocks/v2-newsletter/v2-newsletter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index 622fc01b..fac59348 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -6,8 +6,8 @@ import { getTextLabel, createElement } from '../../scripts/common.js'; const blockName = 'v2-newsletter'; //* init response handling * -const successTitle = `${getTextLabel('Successful submission title')}`; -const successText = `${getTextLabel('Successful submission text')}`; +const successTitle = `${getTextLabel('Success newsletter title')}`; +const successText = `${getTextLabel('Success newsletter text')}`; async function submissionSuccess() { sampleRUM('form:submit'); From 38952e06a3f99dbe970b8a9086189cca274e02dc Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Tue, 21 Nov 2023 14:14:25 +0100 Subject: [PATCH 13/16] PR fixes --- blocks/pardot-form/pardot-form.js | 4 ++-- blocks/v2-newsletter/v2-newsletter.css | 6 ++++-- blocks/v2-newsletter/v2-newsletter.js | 7 +++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/blocks/pardot-form/pardot-form.js b/blocks/pardot-form/pardot-form.js index 5e48b3a9..65ce162b 100644 --- a/blocks/pardot-form/pardot-form.js +++ b/blocks/pardot-form/pardot-form.js @@ -37,8 +37,8 @@ function serialize(obj) { const str = Object.keys(obj).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`); return str.join('&'); } -// eslint-disable-next-line func-names -window.logResult = function (json) { + +window.logResult = function logResult(json) { if (json.result === 'success') { submissionSuccess(); } else if (json.result === 'error') { diff --git a/blocks/v2-newsletter/v2-newsletter.css b/blocks/v2-newsletter/v2-newsletter.css index 3e71cbbb..d63436cf 100644 --- a/blocks/v2-newsletter/v2-newsletter.css +++ b/blocks/v2-newsletter/v2-newsletter.css @@ -45,8 +45,10 @@ margin: 0; } -.v2-newsletter__form-container .button { - text-transform: uppercase; +.v2-newsletter__form-container p { + margin: 0; + max-width: var(--text-block-max-width); + text-align: center; } @media screen and (min-width: 744px) { diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index fac59348..1ffdce0e 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -34,8 +34,7 @@ async function submissionFailure() { } //* end response handling * -// eslint-disable-next-line func-names -window.logResult = function (json) { +window.logResult = function logResult(json) { if (json.result === 'success') { submissionSuccess(); } else if (json.result === 'error') { @@ -44,7 +43,7 @@ window.logResult = function (json) { }; export default async function decorate(block) { - const formLimk = block.firstElementChild.innerText.trim(); + const formLink = block.firstElementChild.innerText.trim(); const html = block.firstElementChild.nextElementSibling.firstElementChild.innerHTML; const container = createElement('div', { classes: `${blockName}__container` }); @@ -62,7 +61,7 @@ export default async function decorate(block) {
subscribe
-
${formLimk}
+
${formLink}
`); From 3271b74b528344b814aee9babec64ed4e14d5aa8 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 22 Nov 2023 16:06:08 +0100 Subject: [PATCH 14/16] Including honeypot pardot extra field --- blocks/v2-forms/v2-forms.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blocks/v2-forms/v2-forms.js b/blocks/v2-forms/v2-forms.js index 913b5688..85bfe986 100644 --- a/blocks/v2-forms/v2-forms.js +++ b/blocks/v2-forms/v2-forms.js @@ -58,7 +58,11 @@ const addForm = async (block) => { name="form-${formName}" action="${formAction}" >${formContent.default} - + +
+ + +
`; From f88ce8ee047bf5e3d8b9b890e4ab9a485c473993 Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 22 Nov 2023 17:49:12 +0100 Subject: [PATCH 15/16] Adding aria-hidden attr to honeypot field --- blocks/v2-forms/v2-forms.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/v2-forms/v2-forms.js b/blocks/v2-forms/v2-forms.js index 85bfe986..e0be3890 100644 --- a/blocks/v2-forms/v2-forms.js +++ b/blocks/v2-forms/v2-forms.js @@ -58,8 +58,8 @@ const addForm = async (block) => { name="form-${formName}" action="${formAction}" >${formContent.default} - -
+ + From 475a6dc8a5ba76c5f2e1cec16e455c41cce67f0c Mon Sep 17 00:00:00 2001 From: Manuel Vara Date: Wed, 22 Nov 2023 18:00:32 +0100 Subject: [PATCH 16/16] removing unnecessary literals --- blocks/v2-newsletter/v2-newsletter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blocks/v2-newsletter/v2-newsletter.js b/blocks/v2-newsletter/v2-newsletter.js index 1ffdce0e..2db5b598 100644 --- a/blocks/v2-newsletter/v2-newsletter.js +++ b/blocks/v2-newsletter/v2-newsletter.js @@ -6,8 +6,8 @@ import { getTextLabel, createElement } from '../../scripts/common.js'; const blockName = 'v2-newsletter'; //* init response handling * -const successTitle = `${getTextLabel('Success newsletter title')}`; -const successText = `${getTextLabel('Success newsletter text')}`; +const successTitle = getTextLabel('Success newsletter title'); +const successText = getTextLabel('Success newsletter text'); async function submissionSuccess() { sampleRUM('form:submit'); @@ -20,8 +20,8 @@ async function submissionSuccess() { form.replaceWith(message); } -const errorTitle = `${getTextLabel('Error submission title')}`; -const errorText = `${getTextLabel('Error submission text')}`; +const errorTitle = getTextLabel('Error submission title'); +const errorText = getTextLabel('Error submission text'); async function submissionFailure() { const form = document.querySelector('form[data-submitting=true]');