Skip to content

Instantly share code, notes, and snippets.

@AndyButland
Last active December 13, 2023 09:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndyButland/9371175d6acf24a5307b053398f08448 to your computer and use it in GitHub Desktop.
Save AndyButland/9371175d6acf24a5307b053398f08448 to your computer and use it in GitHub Desktop.
Umbraco Forms API usage with vanilla JavaScript
@*
Demonstration of using the Umbraco Forms 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.0/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.0/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>
@gitVasile
Copy link

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):

function getFileAsBase64(file: File): Promise<string> {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => {
                const result = reader.result;
                if (typeof result === "string") {
                    resolve("data:" + file.type + ";base64," + result.split(",")[1]);
                } else {
                    reject("Error reading file as base64.");
                }
            };
            reader.onerror = (error) => reject(error);
        });
    }

And that did the tricks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment