Skip to content

Commit

Permalink
Merge pull request #5 from merkle-open/feature/form-block
Browse files Browse the repository at this point in the history
feat: add form block
  • Loading branch information
bchristiani authored Mar 27, 2024
2 parents 70d7516 + 31ed0d4 commit edc6b23
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 0 deletions.
70 changes: 70 additions & 0 deletions blocks/form/form.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.section.form-container h2,
.section.form-container h3 {
margin: 40px 0;
}

.form input:not([type="checkbox"]),
.form textarea {
display: block;
margin-bottom: 20px;
width: 100%;
padding: 8px;
font-family: var(--body-font-family);
font-size: var(--body-font-size-m);
color: var(--background-color);
background-color: transparent;
border: none;
border-bottom: 3px solid var(--highlight-background-color);
outline: none;
}

.form textarea {
min-height: 10rem;
}

.form input:not([type="checkbox"]):focus,
.form textarea:focus {
border-bottom-color: var(--highlight-color);
}

.form input::placeholder {
color: currentcolor;
}

.form input[type="checkbox"] {
margin-right: 10px;
}

.form label {
font-size: var(--body-font-size-s);
}

.form label a:any-link {
color: var(--highlight-color);
}


.form label:empty {
display: none;
}

.form label.required::after {
display: none;
content: "*";
color: var(--highlight-color);
}

.form input[disabled] + label {
opacity: 0.5;
}

/* highlight form */

main .section.highlight .form input:not([type="checkbox"]),
main .section.highlight .form textarea {
border-bottom-color: var(--background-color);
}

main .section.highlight .form label a:any-link {
color: var(--highlight-color);
}
199 changes: 199 additions & 0 deletions blocks/form/form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
function createSelect(fd) {
const select = document.createElement('select');
select.id = fd.Field;
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);
});
if (fd.Mandatory === 'x') {
select.setAttribute('required', 'required');
}
return select;
}

function constructPayload(form) {
const payload = {};
[...form.elements]
.filter((field) => field.id !== 'submit')
.forEach((fe) => {
if (fe.type === 'checkbox') {
if (fe.checked) payload[fe.name] = true;
} else if (fe.name) {
payload[fe.name] = fe.value;
} else if (fe.id) {
payload[fe.id] = fe.value;
}
});
// send date
payload.sent = new Date().toUTCString();
return payload;
}

async function submitForm(form) {
const payload = constructPayload(form);
const resp = await fetch(form.dataset.action, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: payload }),
});
if (resp.ok) {
await resp.text();
return payload;
}
return null;
}

function createButton(fd) {
const button = document.createElement('button');
button.textContent = fd.Label;
button.classList.add('button');
if (fd.Type === 'submit') {
button.addEventListener('click', async (event) => {
const form = button.closest('form');
if (form.checkValidity()) {
event.preventDefault();
button.setAttribute('disabled', '');
const payload = await submitForm(form);
if (payload && fd.ThankYou) {
window.location.href = fd.ThankYou;
}
}
});
}
return button;
}

function createHeading(fd) {
const heading = document.createElement('h3');
heading.textContent = fd.Label;
return heading;
}

function createInput(fd) {
const input = document.createElement('input');
input.type = fd.Type;
input.id = fd.Field;
input.name = fd.Field;
input.value = fd.Value || '';
input.setAttribute('placeholder', fd.Placeholder);
if (fd.Mandatory === 'x') {
input.setAttribute('required', 'required');
}
return input;
}

function createTextArea(fd) {
const input = document.createElement('textarea');
input.id = fd.Field;
input.setAttribute('placeholder', fd.Placeholder);
if (fd.Mandatory === 'x') {
input.setAttribute('required', 'required');
}
return input;
}

function createLabel(fd) {
const label = document.createElement('label');
label.setAttribute('for', fd.Field);
label.textContent = fd.Label;
if (fd.Mandatory === 'x') {
label.classList.add('required');
}
if (fd.LabelLinks) {
const links = JSON.parse(fd.LabelLinks);
Object.keys(links).forEach((key) => {
const link = `<a href="${links[key]}" target="_blank" title="${key}">${key}</a>`;
label.innerHTML = label.innerHTML.replace(key, link);
});
}
return label;
}

function checkForm(form) {
const consent = form.querySelector('input[name="consent"]');
const submit = form.querySelector('button');
const fields = [...form.elements];
const submitReady = fields
.filter((field) => field !== submit)
.every((field) => field.reportValidity());
const consentReady = fields
.filter((field) => field !== consent)
.every((field) => field.checkValidity());
consent[consentReady ? 'removeAttribute' : 'setAttribute']('disabled', true);
consent.checked = consentReady ? consent.checked : false; // uncheck if disabled
submit[submitReady ? 'removeAttribute' : 'setAttribute']('disabled', true);
}

async function createForm(formURL) {
const { pathname } = new URL(formURL);
const resp = await fetch(pathname);
const json = await resp.json();
const form = document.createElement('form');
const rules = [];
// eslint-disable-next-line prefer-destructuring
form.dataset.action = pathname.split('.json')[0];
json.data.forEach((fd) => {
fd.Type = fd.Type || 'text';
const fieldWrapper = document.createElement('div');
const style = fd.Style ? ` form-${fd.Style}` : '';
const fieldId = `form-${fd.Type}-wrapper${style}`;
fieldWrapper.className = fieldId;
fieldWrapper.classList.add('field-wrapper');
switch (fd.Type) {
case 'select':
fieldWrapper.append(createLabel(fd));
fieldWrapper.append(createSelect(fd));
break;
case 'heading':
fieldWrapper.append(createHeading(fd));
break;
case 'checkbox':
fieldWrapper.append(createInput(fd));
fieldWrapper.append(createLabel(fd));
break;
case 'text-area':
fieldWrapper.append(createLabel(fd));
fieldWrapper.append(createTextArea(fd));
break;
case 'submit':
fieldWrapper.append(createButton(fd));
break;
default:
fieldWrapper.append(createLabel(fd));
fieldWrapper.append(createInput(fd));
}

if (fd.Rules) {
try {
rules.push({ fieldId, rule: JSON.parse(fd.Rules) });
} catch (e) {
// eslint-disable-next-line no-console
console.warn(`Invalid Rule ${fd.Rules}: ${e}`);
}
}
form.append(fieldWrapper);
});

form.addEventListener('change', () => checkForm(form));
checkForm(form);
return (form);
}

export default async function decorate(block) {
const form = block.querySelector('a[href$=".json"]');
if (form) {
form.replaceWith(await createForm(form.href));
}
}
5 changes: 5 additions & 0 deletions blocks/thankyou/thankyou.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
div.fullpage {
min-height: calc(100vh - var(--nav-height));
display: flex;
align-items: center;
}
3 changes: 3 additions & 0 deletions blocks/thankyou/thankyou.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function decorate() {

}

0 comments on commit edc6b23

Please sign in to comment.