Last active
June 7, 2024 09:40
-
-
Save AndyButland/9371175d6acf24a5307b053398f08448 to your computer and use it in GitHub Desktop.
Umbraco Forms API usage with vanilla JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@* | |
Demonstration of using the Umbraco Forms 13 API for retrieving the definition of a form, rendering it and posting an entry. | |
It's based off an Umbraco content item with a property with alias "Form", of type "Form Picker". | |
*@ | |
@using Microsoft.AspNetCore.Antiforgery | |
@using Microsoft.Extensions.Options; | |
@using Umbraco.Cms.Web.Common.PublishedModels; | |
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.AJaxformPage> | |
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; | |
@using Umbraco.Forms.Core.Configuration; | |
@inject IAntiforgery antiforgery | |
@{ | |
var tokenSet = antiforgery.GetAndStoreTokens(Context); | |
Guid? formKey = Model.Form.Value; | |
Guid contentKey = Model.Key; | |
} | |
@inject IOptions<Recaptcha2Settings> OptionsRecaptcha2Settings | |
@{ | |
var recaptcha2Settings = OptionsRecaptcha2Settings.Value; | |
} | |
@inject IOptions<Recaptcha3Settings> OptionsRecaptcha3Settings | |
@{ | |
var recaptcha3Settings = OptionsRecaptcha3Settings.Value; | |
} | |
<html> | |
<head> | |
<title>AJAX Form</title> | |
<style type="text/css"> | |
.validation-summary { | |
color: red | |
} | |
.success-message { | |
color: green | |
} | |
</style> | |
</head> | |
<body> | |
<h1>AJAX Form</h1> | |
@if (formKey.HasValue) | |
{ | |
<div id="form"></div> | |
} | |
<script type="text/javascript"> | |
const culture = "it"; | |
async function getAndRenderForm(formId, contentId) { | |
if (!formId) { | |
return; | |
} | |
let response = await fetch("/umbraco/forms/api/v1/definitions/" + formId + "?contentId=" + contentId + "&culture=" + culture); | |
if (response.status === 200) { | |
let data = await response.json(); | |
renderForm(data); | |
} | |
} | |
function renderForm(data) { | |
const wrapperElement = document.getElementById("form"); | |
renderPostSubmitMessage(wrapperElement, data.id, data.messageOnSubmit); | |
renderValidationSummary(wrapperElement, data.id); | |
const formElement = document.createElement("form"); | |
addAttribute(formElement, "id", "form-" + data.id); | |
for (let p = 0; p < data.pages.length; p++) { | |
for (let fs = 0; fs < data.pages[p].fieldsets.length; fs++) { | |
for (let c = 0; c < data.pages[p].fieldsets[fs].columns.length; c++) { | |
for (let f = 0; f < data.pages[p].fieldsets[fs].columns[c].fields.length; f++) { | |
renderField(formElement, data.pages[p].fieldsets[fs].columns[c].fields[f]); | |
} | |
} | |
} | |
} | |
renderButton(formElement); | |
wrapperElement.appendChild(formElement); | |
} | |
function renderPostSubmitMessage(wrapperElement, formId, message) { | |
const validationSummaryElement = document.createElement("div"); | |
validationSummaryElement.classList.add("success-message"); | |
validationSummaryElement.innerText = message; | |
validationSummaryElement.style.display = "none"; | |
addAttribute(validationSummaryElement, "id", "success-message-" + formId); | |
wrapperElement.appendChild(validationSummaryElement); | |
} | |
function renderValidationSummary(wrapperElement, formId) { | |
const validationSummaryElement = document.createElement("div"); | |
validationSummaryElement.classList.add("validation-summary"); | |
addAttribute(validationSummaryElement, "id", "validation-summary-" + formId); | |
wrapperElement.appendChild(validationSummaryElement); | |
} | |
function renderField(formElement, field) { | |
let fieldElement = null; | |
switch (field.type.name.toLowerCase()) { | |
case "short answer": | |
fieldElement = getTextFieldElement(field); | |
break; | |
case "long answer": | |
fieldElement = getTextAreaElement(field); | |
break; | |
case "dropdown": | |
fieldElement = getDropDownElement(field); | |
break; | |
case "data consent": | |
fieldElement = getDataConsentElement(field); | |
break; | |
case "file upload": | |
fieldElement = getFileUploadElement(field); | |
break; | |
case "recaptcha2": | |
fieldElement = getRecaptcha2Element(field); | |
break; | |
case "recaptcha v3 with score": | |
fieldElement = getRecaptcha3Element(formElement, field); | |
break; | |
} | |
if (fieldElement) { | |
formElement.appendChild(fieldElement); | |
} | |
} | |
function getTextFieldElement(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
addFieldLabelElement(fieldWrapperElement, field); | |
const fieldElement = document.createElement("input"); | |
addAttribute(fieldElement, "id", field.id); | |
addAttribute(fieldElement, "name", field.alias); | |
addAttributeFromFieldSettings(fieldElement, "type", field.settings, "fieldType", "text"); | |
addAttributeFromFieldSettings(fieldElement, "maxlength", field.settings, "maximumLength", ""); | |
addAttributeFromFieldSettings(fieldElement, "placeholder", field.settings, "placeholder", ""); | |
addAttributeFromFieldSettings(fieldElement, "value", field.settings, "defaultValue", ""); | |
fieldWrapperElement.appendChild(fieldElement); | |
return fieldWrapperElement; | |
} | |
function getTextAreaElement(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
addFieldLabelElement(fieldWrapperElement, field); | |
const fieldElement = document.createElement("textarea"); | |
addAttribute(fieldElement, "id", field.id); | |
addAttribute(fieldElement, "name", field.alias); | |
addAttributeFromFieldSettings(fieldElement, "maxlength", field.settings, "maximumLength", ""); | |
addAttributeFromFieldSettings(fieldElement, "placeholder", field.settings, "placeholder", ""); | |
addAttributeFromFieldSettings(fieldElement, "rows", field.settings, "numberOfRows", "5"); | |
addAttributeFromFieldSettings(fieldElement, "value", field.settings, "defaultValue", ""); | |
fieldWrapperElement.appendChild(fieldElement); | |
return fieldWrapperElement; | |
} | |
function getDropDownElement(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
addFieldLabelElement(fieldWrapperElement, field); | |
const fieldElement = document.createElement("select"); | |
addAttribute(fieldElement, "id", field.id); | |
addAttribute(fieldElement, "name", field.alias); | |
const selectPrompt = field.settings["selectPrompt"] ? field.settings["selectPrompt"] : ""; | |
addDropDownOptionElement(fieldElement, "", selectPrompt); | |
for (let i = 0; i < field.preValues.length; i++) { | |
addDropDownOptionElement(fieldElement, field.preValues[i].value, field.preValues[i].caption); | |
} | |
fieldWrapperElement.appendChild(fieldElement); | |
return fieldWrapperElement; | |
} | |
function addDropDownOptionElement(selectElement, value, text) { | |
const optionElement = document.createElement("option"); | |
addAttribute(optionElement, "value", value); | |
optionElement.innerText = text; | |
selectElement.appendChild(optionElement); | |
} | |
function getDataConsentElement(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
const fieldElement = document.createElement("input"); | |
addAttribute(fieldElement, "id", field.id); | |
addAttribute(fieldElement, "name", field.alias); | |
addAttribute(fieldElement, "type", "checkbox"); | |
fieldWrapperElement.appendChild(fieldElement); | |
const messageElement = document.createElement("span"); | |
messageElement.innerText = field.settings["acceptCopy"]; | |
fieldWrapperElement.appendChild(messageElement); | |
return fieldWrapperElement; | |
} | |
function getFileUploadElement(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
addFieldLabelElement(fieldWrapperElement, field); | |
const fieldElement = document.createElement("input"); | |
addAttribute(fieldElement, "id", field.id); | |
addAttribute(fieldElement, "name", field.alias); | |
addAttribute(fieldElement, "type", "file"); | |
if (field.fileUploadOptions.allowAllUploadExtensions === false) { | |
var allowedUploadExtensions = field.fileUploadOptions.allowedUploadExtensions.map(function (value) { | |
return "." + value; | |
}) | |
var acceptValues = allowedUploadExtensions.join(","); | |
addAttribute(fieldElement, "accept", acceptValues); | |
} | |
if (field.fileUploadOptions.allowMultipleFileUploads) { | |
addAttribute(fieldElement, "multiple"); | |
} | |
fieldWrapperElement.appendChild(fieldElement); | |
return fieldWrapperElement; | |
} | |
function getRecaptcha2Element(field) { | |
const fieldWrapperElement = document.createElement("div"); | |
const scriptElement = document.createElement("script"); | |
addAttribute(scriptElement, "src", "https://www.google.com/recaptcha/api.js"); | |
addAttribute(scriptElement, "async"); | |
addAttribute(scriptElement, "defer"); | |
addAttribute(scriptElement, "type", "application/javascript"); | |
fieldWrapperElement.appendChild(scriptElement); | |
const fieldElement = document.createElement("div"); | |
addAttribute(fieldElement, "class", "g-recaptcha"); | |
addAttributeFromFieldSettings(fieldElement, "data-theme", field.settings, "theme", ""); | |
addAttributeFromFieldSettings(fieldElement, "data-size", field.settings, "size", ""); | |
addAttribute(fieldElement, "data-sitekey", "@(recaptcha2Settings.PublicKey)"); | |
fieldWrapperElement.appendChild(fieldElement); | |
return fieldWrapperElement; | |
} | |
function getRecaptcha3Element(formElement, field) { | |
const fieldWrapperElement = document.createElement("div"); | |
const scriptElement = document.createElement("script"); | |
addAttribute(scriptElement, "src", "https://www.google.com/recaptcha/api.js?render=@(recaptcha3Settings.SiteKey)"); | |
addAttribute(scriptElement, "type", "application/javascript"); | |
scriptElement.onload = function () { | |
var timerFunction = function (hiddenField, siteKey) { | |
window.grecaptcha.execute(siteKey, { action: "umbracoform_submit" }).then(function (token) { | |
// Enable the submit button now we have a token. | |
formElement.querySelector('[type=submit]').removeAttribute("disabled"); | |
hiddenField.value = token; | |
}); | |
setTimeout(timerFunction, 60 * 1000, hiddenField, siteKey); | |
}; | |
// Disable the submit button for this form, until we actually have a key from Google reCAPTCHA. | |
formElement.querySelector("[type=submit]").setAttribute("disabled", "disabled"); | |
var hiddenField = document.getElementById(field.id); | |
window.grecaptcha.ready(function () { | |
timerFunction(hiddenField, "@(recaptcha3Settings.SiteKey)"); | |
}); | |
}; | |
fieldWrapperElement.appendChild(scriptElement); | |
const inputElement = document.createElement("input"); | |
addAttribute(inputElement, "type", "hidden"); | |
addAttribute(inputElement, "id", field.id); | |
addAttribute(inputElement, "name", "g-recaptcha-response"); | |
fieldWrapperElement.appendChild(inputElement); | |
return fieldWrapperElement; | |
} | |
function addAttribute(element, name, value) { | |
const attr = document.createAttribute(name); | |
if (value) { | |
attr.value = value; | |
} | |
element.setAttributeNode(attr); | |
} | |
function addFieldLabelElement(fieldWrapperElement, field) { | |
const labelElement = document.createElement("label"); | |
labelElement.innerText = field.caption + ": "; | |
const forAttr = document.createAttribute("for"); | |
forAttr.value = field.id; | |
labelElement.setAttributeNode(forAttr); | |
fieldWrapperElement.appendChild(labelElement); | |
if (field.helpText && field.helpText.length > 0) { | |
const helpTextElement = document.createElement("small"); | |
helpTextElement.innerText = field.helpText; | |
fieldWrapperElement.appendChild(helpTextElement); | |
} | |
} | |
function addAttributeFromFieldSettings(fieldElement, attrName, settings, settingKey, defaultValue) { | |
const attr = document.createAttribute(attrName); | |
if (settings[settingKey] && settings[settingKey].length > 0) { | |
attr.value = settings[settingKey]; | |
} else { | |
attr.value = defaultValue; | |
} | |
if (attr.value !== "") { | |
fieldElement.setAttributeNode(attr); | |
} | |
} | |
function renderButton(formElement) { | |
const buttonElement = document.createElement("button"); | |
buttonElement.innerText = "Submit" | |
addAttribute(buttonElement, "type", "submit"); | |
buttonElement.addEventListener("click", submitForm); | |
formElement.appendChild(buttonElement); | |
} | |
async function submitForm(e) { | |
e.preventDefault(); | |
const formId = this.form.getAttribute("id").replace("form-", ""); | |
const validationSummaryElement = document.getElementById("validation-summary-" + formId); | |
clearValidationErrors(validationSummaryElement); | |
const data = { | |
contentKey: "@contentKey", | |
culture: culture, | |
values: {} | |
}; | |
const formData = new FormData(this.form); | |
for (const [key, value] of formData) { | |
// For file uploads, we need to transform the value into a structure containing | |
// the file name and the base 64 encoded file contents. | |
// NOTE: file upload processing is only available from Forms 10.2.2 and 11.0.1. | |
if (isFileUpload(value)) { | |
var fileName = value.name; | |
var fileContents = await getFileAsBase64(value); | |
// We could have multiple files on a field, so need to provide as an array. | |
if (!data.values[key]) { | |
data.values[key] = []; | |
} | |
data.values[key].push({ | |
fileName: fileName, | |
fileContents: fileContents | |
}); | |
} | |
else { | |
data.values[key] = value; | |
} | |
} | |
let response = await fetch("/umbraco/forms/api/v1/entries/" + formId, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"@tokenSet.HeaderName": "@tokenSet.RequestToken" | |
}, | |
body: JSON.stringify(data), | |
}); | |
if (response.status === 202) { | |
showPostSubmitMessage(formId); | |
} | |
else if (response.status === 422) { | |
let validationErrors = await response.json(); | |
renderValidationErrors(validationErrors, validationSummaryElement); | |
} | |
else { | |
console.log("Error (" + response.status + "): " + response.statusText); | |
} | |
} | |
function clearValidationErrors(validationSummaryElement) { | |
validationSummaryElement.replaceChildren(); | |
} | |
function isFileUpload(value) { | |
return 'File' in window && value instanceof File; | |
} | |
function getFileAsBase64(file) { | |
return new Promise((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.readAsDataURL(file); | |
reader.onload = () => resolve(reader.result); | |
reader.onerror = error => reject(error); | |
}); | |
} | |
function showPostSubmitMessage(formId) { | |
document.getElementById("form-" + formId).style.display = "none"; | |
document.getElementById("success-message-" + formId).style.display = "block"; | |
} | |
function renderValidationErrors(validationErrors, validationSummaryElement) { | |
const validationSummaryHeaderElement = document.createElement("div"); | |
validationSummaryHeaderElement.innerText = "Validation failed. Please resolve the following errors:"; | |
validationSummaryElement.appendChild(validationSummaryHeaderElement); | |
const validationSummaryListElement = document.createElement("ul"); | |
for (const [key, value] of Object.entries(validationErrors.errors)) { | |
for (let i = 0; i < value.length; i++) { | |
const validationSummaryListEntryElement = document.createElement("li"); | |
validationSummaryListEntryElement.innerText = value[i]; | |
validationSummaryListElement.appendChild(validationSummaryListEntryElement); | |
} | |
} | |
validationSummaryElement.appendChild(validationSummaryListElement); | |
} | |
getAndRenderForm("@formKey", "@contentKey"); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello and thank you for the example you provided. For the fileUpload your function is returning the base64 without file type and I get an error.
I had to change the function getFileAsBase64 to this one(I am using typescript):
And that did the tricks.