Skip to content

Instantly share code, notes, and snippets.

@vivianspencer
Last active January 5, 2025 15:09
Show Gist options
  • Save vivianspencer/f10da933c919fc04a65d2a75b0f69199 to your computer and use it in GitHub Desktop.
Save vivianspencer/f10da933c919fc04a65d2a75b0f69199 to your computer and use it in GitHub Desktop.
Act-On form embed code
var _aoFormLoader = (function(w) {
var stylesLoaded = false;
var captchaLoaded = false;
var gfLoaded = false;
var htmlId = false;
var scheme = document.location.protocol + "//";
var loadForm = function(options) {
loadStylesheet(options);
var previewMarkup = '<p style="text-align: center;">Unable to preview form, it has reached the submission cap or expired.</p>';
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
var response;
if (e.target.responseType === 'json') {
response = e.target.response || false;
} else {
response = (e.target.response) ? JSON.parse(e.target.response) : false;
}
if (!response) {
return false;
} else if (response && typeof response.processedTemplate === 'undefined' && response.linkURL && response.linkURL != '') {
window.location = response.linkURL;
return false;
}
var idArr = options.id.split(":");
htmlId = (options.uniqueId) ? options.id + options.uniqueId : options.id;
divId = (options.uniqueId) ? idArr[0] + options.uniqueId : idArr[0];
var existingDiv = document.getElementById('aoform-' + divId);
if (existingDiv)
existingDiv.parentNode.removeChild(existingDiv);
var div = document.createElement('div');
var scriptTag = document.getElementById('aoform-script-' + htmlId);
div.id = 'aoform-' + divId;
scriptTag.parentNode.insertBefore(div, scriptTag);
div.innerHTML = response && response.processedTemplate ? response.processedTemplate : previewMarkup;
//Set referrer
div.querySelector("input[name='ao_refurl']").value = document.referrer;
div.querySelector("input[name='ao_refemail']").value = getParameterByName("aoRefEmail");
var ajaxSubmit = response && response.formProperties && response.formProperties.hasAjaxResponse ? response.formProperties.hasAjaxResponse : false;
loadCaptcha();
loadGoogleFonts();
formValidationEvents(div, ajaxSubmit);
// init the rule listeners here
if (response.formProperties && response.formProperties.hasRules === true) {
loadRules(options.accountId, options.id, response.watchFields, options);
}
//Post Load Callback
if (typeof aoPostLoadCallback === 'function') {
aoPostLoadCallback();
}
//Post Load Callback
if (typeof aoPostLoadCallbackInternal === 'function') {
aoPostLoadCallbackInternal();
}
// Form Prefill
if (response.prefill && Object.keys(response.prefill).length > 0) {
var fields = Object.keys(response.prefill);
fields.forEach(function(field) {
var input = div.querySelector("input[name='" + field + "']");
var select = div.querySelector("select[name='" + field + "']");
if (!input && !select) {
return;
}
if (input) {
if (input.type == "radio") {
var radios = div.querySelectorAll("input[name='" + field + "']");
for (var i = 0; i < radios.length; i++) {
if (radios[i].value == response.prefill[field]) {
radios[i].checked = true;
}
}
} else if (input.type == "checkbox") {
var checkboxes = div.querySelectorAll("input[name='" + field + "']");
var values = response.prefill[field].split(/[;,\^]/);
for (var i = 0; i < checkboxes.length; i++) {
for (var j = 0; j < values.length; j++) {
if (checkboxes[i].value == values[j]) {
checkboxes[i].checked = true;
}
}
}
} else if (input.type == "text") {
div.querySelector("input[name='" + field + "']").value = response.prefill[field];
}
} else if (select) {
for (var i = 0; i < select.options.length; i++) {
if (select.options[i].text == response.prefill[field]) {
select.options[i].selected = true;
}
}
}
});
}
};
var url;
if (options.isTemp) {
url = scheme + options.domain + '/acton/internalapi/FormBuilder/' + options.accountId + '/form/temp/' + options.id + '?' + new Date().getTime() + ((options.noStyle) ? "&noStyle=1" : "");
} else {
//url = scheme + options.domain + '/acton/internalapi/FormBuilder/' + options.accountId + '/form/' + options.id + '?' + new Date().getTime() + ((options.noStyle) ? "&noStyle=1" : "");
url = scheme + options.domain + "/acton/openapi/form/v1/" + options.accountId + "/" + options.id + '?' + new Date().getTime() + ((options.noStyle) ? "&noStyle=1" : "" + ((options.prefill) ? "&prefill=1" : ""));
}
oReq.open('GET', url, true);
oReq.responseType = 'json';
oReq.send();
};
var loadStylesheet = function(options) {
if (!stylesLoaded) {
var styleTag = document.createElement("link");
styleTag.rel = "stylesheet";
styleTag.type = "text/css";
styleTag.href = scheme + options.domain + "/acton/content/form_flattener.css";
styleTag.media = "all";
document.getElementsByTagName('head')[0].appendChild(styleTag);
stylesLoaded = true;
}
};
var loadCaptcha = function() {
if (!captchaLoaded) {
var scriptTag = document.createElement("script");
scriptTag.src = "https://www.google.com/recaptcha/api.js";
scriptTag.defer = true;
scriptTag.async = true;
document.getElementsByTagName('head')[0].appendChild(scriptTag);
captchaLoaded = true;
}
};
var loadGoogleFonts = function() {
if (!gfLoaded) {
var scriptTag = document.createElement("script");
scriptTag.src = "https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js";
scriptTag.defer = true;
scriptTag.async = true;
document.getElementsByTagName('head')[0].appendChild(scriptTag);
gfLoaded = true;
}
};
var evalFacts = function(accountId, formId, watchFields, options) {
var fact = {
data: {}
};
watchFields.forEach(function(field) {
var fieldElm = document.getElementById(field);
if (fieldElm.nodeName == "DIV" && fieldElm.classList.contains("ao-combo-layout")) {
var radios = fieldElm.querySelectorAll("input[type='radio'],input[type='checkbox']");
fact.data[field] = "";
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) {
fact.data[field] += radios[i].value;
}
}
} else {
fact.data[field] = document.getElementById(field).value;
}
});
if (formId.indexOf(':') > -1) {
formId = formId.substr(0, formId.indexOf(':'))
}
var body = {
payload: {
accountId: accountId,
key: formId,
facts: [fact]
}
};
var xhr = new XMLHttpRequest();
xhr.open('PUT', scheme + options.domain + '/acton/openapi/form/v1/' + options.accountId + '/' + formId + '/factEval');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(body));
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
var response = JSON.parse(xhr.response);
//Go through the non true results first and set base display
for (var i = 0; i < response.results[0].result.length; i++) {
var result = response.results[0].result[i].result;
var ruleName = response.results[0].result[i].name;
var consequence = response.rules[ruleName].consequences;
if (!result) {
for (var j = 0; j < consequence.length; j++) {
var state = consequence[j].state;
var field = consequence[j].field;
var elm = document.getElementById("block-" + field);
switch (state) {
case "show":
elm.style.display = "none";
elm.classList.add("ao-condition-hide");
break;
case "hide":
elm.style.display = "block";
elm.classList.remove("ao-condition-hide");
break;
}
}
}
}
//Now go through the true results first and set correct display
for (var i = 0; i < response.results[0].result.length; i++) {
var result = response.results[0].result[i].result;
var ruleName = response.results[0].result[i].name;
var consequence = response.rules[ruleName].consequences;
if (result) {
for (var j = 0; j < consequence.length; j++) {
var state = consequence[j].state;
var field = consequence[j].field;
var elm = document.getElementById("block-" + field);
switch (state) {
case "show":
elm.style.display = "block";
elm.classList.remove("ao-condition-hide");
break;
case "hide":
elm.style.display = "none";
elm.classList.add("ao-condition-hide");
break;
}
}
}
}
} else {
// an error occurred
}
}
};
var loadRules = function(accountId, formId, watchFields, options) {
if (!watchFields) return;
//Remove duplicate watches
var watchFieldsSeen = {};
var watchFieldsUnique = watchFields.filter(function(item) {
return watchFieldsSeen.hasOwnProperty(item) ? false : (watchFieldsSeen[item] = true);
});
var evalEvent = function(event) {
// put a debounce in here so we're making requests on every keystroke
if (!el.classList.contains("ao-form-error")) {
var result = evalFacts(accountId, formId, watchFieldsUnique, options);
}
};
watchFieldsUnique.forEach(function(field) {
var el = document.getElementById(field);
if (el.nodeName == "SELECT") {
el.addEventListener('change', function(event) {
// put a debounce in here so we're making requests on every keystroke
if (!el.classList.contains("ao-form-error")) {
var result = evalFacts(accountId, formId, watchFieldsUnique, options);
}
}, false);
} else if (el.nodeName == "DIV" && el.classList.contains("ao-combo-layout")) {
var inputs = el.querySelectorAll("input");
for (i = 0; i < inputs.length; i++) {
inputs[i].addEventListener('click', function(event) {
// put a debounce in here so we're making requests on every keystroke
if (!el.classList.contains("ao-form-error")) {
var result = evalFacts(accountId, formId, watchFieldsUnique, options);
}
}, false);
}
} else {
el.addEventListener('keyup', function(event) {
// put a debounce in here so we're making requests on every keystroke
if (!el.classList.contains("ao-form-error")) {
var result = evalFacts(accountId, formId, watchFieldsUnique, options);
}
}, false);
}
});
//Initial Load
evalFacts(accountId, formId, watchFieldsUnique, options);
};
function getParameterByName(name) {
url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)", "i"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
var formValidationEvents = function(form, ajaxSubmit) {
var actonForms = form.querySelectorAll("form.ao-form");
var findRecaptchaElements = function(form, hasError) {
//Find the recaptcha wrapper
var recaptchaWrapper = form.querySelectorAll(".ao-recaptcha-error-wrapper");
if (recaptchaWrapper.length > 0) {
if (hasError) {
//If found and has error then apply the style that will show the red border around the element
recaptchaWrapper[0].classList.add("ao-recaptcha-error");
} else {
//Remove the style because the user has attempted to work with the recaptcha again
recaptchaWrapper[0].classList.remove("ao-recaptcha-error");
}
}
//Find the element that will show the failed to completed the recaptcha
var recaptchaMsg = form.querySelectorAll(".ao-recaptcha-error-robot-message");
if (recaptchaMsg.length > 0) {
if (hasError) {
//Show the text
recaptchaMsg[0].style.display = "block";
} else {
//Hide the text
recaptchaMsg[0].style.display = "none";
}
}
};
var invalidCaptcha = function(form) {
//Wait for the Recaptcha widget before we build the error logging aorund it
setTimeout(function() {
//Apply the styling that says the user failed to validate the Recaptcha
findRecaptchaElements(form, true);
}, 1000);
//Keep checking if the user has attempted to validate the Recaptcha, we will know because the token will go into the hidden input the cap sends on submission
var recaptchaInterval = setInterval(function() {
var recaptchaBoxCode = form.querySelectorAll("#g-recaptcha-response");
//If we find the element and the value is not empty then a user has interacted with the cap, validation happens on the submission not now
if (recaptchaBoxCode.length > 0 && recaptchaBoxCode[0].value !== '') {
//Remove the styling that was generated by the error
findRecaptchaElements(form, false);
//Remove the interval that listens for the check
window.clearInterval(recaptchaInterval);
}
}, 2000);
};
for (var i = 0; i < actonForms.length; i++) {
//Set On Blur Events For All Inputs
var validationFields = actonForms[i].querySelectorAll(".ao-form-field[data-validator]:not([data-attached])");
var validationCombos = actonForms[i].querySelectorAll(".ao-combo-layout[data-validator]:not([data-attached])");
var validationDates = actonForms[i].querySelectorAll(".ao-form-field-date-wrapper[data-validator]:not([data-attached])");
var dynamicHiddenFields = actonForms[i].querySelectorAll(".ao-hidden-block-dynamic");
//check if the form id matches the param sent back on the user submission redirect of the formid that failed user submit
if (htmlId.indexOf(getParameterByName("validRecaptcha")) > -1) {
invalidCaptcha(form);
}
if (actonForms[i].hasAttribute('data-validate-blur')) {
for (var j = 0; j < validationFields.length; j++) {
var currentField = validationFields[j];
(function(currentField) {
currentField.addEventListener("keyup", function() {
validateField(currentField);
})
})(currentField);
}
}
//Set On Submit Event
var currentForm = actonForms[i];
(function(currentForm, ajaxSubmit) {
currentForm.addEventListener("submit", function(e) {
var passedValidation = true;
for (var i = 0; i < validationFields.length; i++) {
//Check visibility of field. offsetWidth/Height of null = not visible
if (!!(validationFields[i].offsetWidth || validationFields[i].offsetHeight || validationFields[i].getClientRects().length)) {
if (!validateField(validationFields[i])) {
passedValidation = false;
}
}
}
for (var i = 0; i < validationCombos.length; i++) {
//Check visibility of field. offsetWidth/Height of null = not visible
if (!!(validationCombos[i].offsetWidth || validationCombos[i].offsetHeight || validationCombos[i].getClientRects().length)) {
if (!validateField(validationCombos[i], {
isCombo: true
})) {
passedValidation = false;
}
}
}
for (var i = 0; i < validationDates.length; i++) {
if (!!(validationDates[i].offsetWidth || validationDates[i].offsetHeight || validationDates[i].getClientRects().length)) {
if (!validateField(validationDates[i], {
isDate: true
})) {
passedValidation = false;
}
}
}
for (var i = 0; i < dynamicHiddenFields.length; i++) {
var fieldMatch = dynamicHiddenFields[i].getAttribute("data-dynamic-match");
var fieldDefault = dynamicHiddenFields[i].getAttribute("data-dynamic-default");
dynamicHiddenFields[i].value = getParameterByName(fieldMatch) || fieldDefault;
}
//Set Date fields
var dateBlocks = currentForm.querySelectorAll(".ao-date-block");
for (var i = 0; i < dateBlocks.length; i++) {
var dateBlock = dateBlocks[i];
var dateBlockInput = dateBlock.querySelector(".ao-form-field-date-input");
var dateBlockPartials = dateBlock.querySelectorAll(".ao-form-field-date");
var dateVal = "";
for (var j = 0; j < dateBlockPartials.length; j++) {
var partial = dateBlockPartials[j];
var format = partial.getAttribute("data-format");
if (format == "MM" || format == "DD" || format == "YY" || format == "YYYY") {
if (dateVal) {
dateVal = dateVal + "/";
}
dateVal += partial.value;
}
if (format == "h" || format == "mm") {
if (dateVal && format == "h") {
dateVal = dateVal + " ";
}
dateVal += partial.value;
if (format == "h" && partial.value) {
dateVal = dateVal + ":";
}
}
if (format == "tt") {
if (dateVal) {
dateVal = dateVal + " ";
}
dateVal += partial.value;
}
}
dateBlockInput.value = dateVal;
}
var captcha = document.querySelector('#g-recaptcha-response');
// if a captcha is being used check it
if (captcha !== null) {
var captchaValue = captcha.value;
// if the captcha value is empty invalidate the form
if (captchaValue === '') {
passedValidation = false;
invalidCaptcha(currentForm);
}
}
if (!passedValidation || ajaxSubmit) {
e.preventDefault();
}
if (passedValidation && ajaxSubmit) {
var data = new FormData(currentForm);
var req = new XMLHttpRequest();
req.onload = function() {
var response;
try {
response = JSON.parse(this.response);
} catch (e) {
response = this.response;
}
if (typeof response === 'object' && response.error && response.error == "captcha") {
invalidCaptcha(currentForm);
} else {
var divId = currentForm.id.replace("ao-form", "aoform");
var div = document.getElementById(divId);
div.innerHTML = response;
}
};
req.open(currentForm.method, currentForm.action);
req.withCredentials = true;
req.send(data);
}
})
})(currentForm, ajaxSubmit);
}
};
var validateField = function(field, options) {
var o = options ? options : {};
var validators = field.getAttribute('data-validator').split("|");
var errorSpan = field.parentNode.querySelector(".ao-form-error-message");
errorSpan.innerHTML = "&nbsp;";
var errorMessageArr = [];
var errorMessageOverrideArr = (field.getAttribute('data-error-message')) ? field.getAttribute('data-error-message').split("::") : false;
field.classList.remove("ao-form-error");
for (var i = 0; i < errorMessageOverrideArr.length; i++) {
var errorMessageOverride = errorMessageOverrideArr[i].split("|");
errorMessageArr[errorMessageOverride[0]] = errorMessageOverride[1];
}
for (var j = 0; j < validators.length; j++) {
var validationType = validators[j];
var hasErrors = false;
switch (validationType) {
case "required":
if (o.isCombo) {
var validationComboFields = field.querySelectorAll("input");
var hasRequired = false;
for (var k = 0; k < validationComboFields.length; k++) {
if (validationComboFields[k].checked) {
hasRequired = true;
}
}
if (!hasRequired) {
hasErrors = true;
}
} else if (o.isDate) {
var validationDateFields = field.querySelectorAll("input, select");
var hasRequired = true;
for (var l = 0; l < validationDateFields.length; l++) {
if (!validationDateFields[l].value.trim()) {
validationDateFields[l].classList.add("ao-form-error");
hasRequired = false;
} else {
validationDateFields[l].classList.remove("ao-form-error");
}
}
if (!hasRequired) {
hasErrors = true;
}
} else if (!field.value.trim()) {
hasErrors = true;
}
break;
case "date":
if (o.isDate) {
var validationDateFields = field.querySelectorAll("input");
for (var m = 0; m < validationDateFields.length; m++) {
var dateType = validationDateFields[m].getAttribute("placeholder");
switch (dateType) {
case "MM":
if (validationDateFields[m].value && !/^([0]?[1-9]|1[012])$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
case "DD":
if (validationDateFields[m].value && !/^([0]?[1-9]|[12]\d|3[01])$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
case "YY":
if (validationDateFields[m].value && !/^(0?[1-9]|[1-9][0-9])$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
case "YYYY":
if (validationDateFields[m].value && !/^[0-9]{4}$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
case "h":
if (validationDateFields[m].value && !/^([0]?[1-9]|1[012])$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
case "mm":
if (validationDateFields[m].value && !/^[0-5][0-9]$/.test(validationDateFields[m].value)) {
validationDateFields[m].classList.add("ao-form-error");
hasErrors = true;
} else {
validationDateFields[m].classList.remove("ao-form-error");
}
break;
}
}
}
break;
case "email":
if (field.value && !/^[a-zA-Z0-9]+([\w'._+-])*@[\w-]+\.[\w.]{2,}$/.test(field.value)) {
hasErrors = true;
}
break;
case "numeric":
if (field.value && !/^\d+$/.test(field.value)) {
hasErrors = true;
}
break;
case "alphabetic":
if (field.value && !/^[a-zA-Z]+$/.test(field.value)) {
hasErrors = true;
}
break;
case "alphanumeric":
if (field.value && !/^[a-zA-Z0-9]*$/.test(field.value)) {
hasErrors = true;
}
break;
case "minlength":
if (field.value && field.value.length < field.getAttribute('data-minlength') || 0) {
hasErrors = true;
}
break;
case "maxlength":
if (field.value && field.value.length > field.getAttribute('maxlength') || 0) {
hasErrors = true;
}
break;
case "match":
if (field.value != document.getElementById(field.getAttribute('data-match')).value) {
hasErrors = true;
}
break;
case "phoneus":
if (field.value && !/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?\s?[2-9]([02-9]\d|1[02-9])-?\d{4}$/.test(field.value)) {
hasErrors = true;
}
break;
case "url":
if (field.value && !/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:/~+#-]*[\w@?^=%&amp;/~+#-])?/.test(field.value)) {
hasErrors = true;
}
break;
case "custom":
var pattern = new RegExp(field.getAttribute('data-custom'));
if (field.value && !pattern.test(field.value)) {
hasErrors = true;
}
break;
}
if (hasErrors) {
if (!o.isDate)
field.classList.add("ao-form-error");
errorSpan.innerHTML = (errorMessageArr[validationType]) ? errorMessageArr[validationType] : "Invalid";
return false;
}
}
return true;
};
return {
load: function(opts) {
loadForm(opts);
},
loadAll: function() {
for (var i = 0; i < w._aoForms.length; i++) {
loadForm(w._aoForms[i]);
}
}
};
}(window));
/*** FormData for older browsers ***/
(function(w) {
if (w.FormData)
return;
function FormData() {
this.fake = true;
this.boundary = "--------FormData" + Math.random();
this._fields = [];
}
FormData.prototype.append = function(key, value) {
this._fields.push([key, value]);
}
FormData.prototype.toString = function() {
var boundary = this.boundary;
var body = "";
this._fields.forEach(function(field) {
body += "--" + boundary + "\r\n";
// file upload
if (field[1].name) {
var file = field[1];
body += "Content-Disposition: form-data; name=\"" + field[0] + "\"; filename=\"" + file.name + "\"\r\n";
body += "Content-Type: " + file.type + "\r\n\r\n";
body += file.getAsBinary() + "\r\n";
} else {
body += "Content-Disposition: form-data; name=\"" + field[0] + "\";\r\n\r\n";
body += field[1] + "\r\n";
}
});
body += "--" + boundary + "--";
return body;
}
w.FormData = FormData;
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment