This document is for an older version of
Freeform
. View latest version →Submitting a form using AJAX
WARNING
AJAX currently will not work with multi-page forms.
Freeform now has built-in automated AJAX submit support. To enable, simply check the Enable AJAX checkbox in the Form Settings tab inside Composer. Freeform will then handle the rest (as long as you're using a standard formatting template - very custom formatting templates may not work properly).
If you wish to customize the success and error messages for the form, you'd have to edit (or add extra override JS to your page) in the template/formatting template to control the message and styling. You can additionally add the translate |t
filter to it to allow static translations in your Freeform translation file to handle other site languages. This additional code isn't necessary at all if you only want to translate the success and error strings and you're using a formatting template that already contains the following JS override code. In Freeform they default to: Form has been submitted successfully!
and Error! Please review the form and try submitting again.
. Also see add/control redirect for AJAX-enabled forms.
<script>
window.renderFormSuccess = function (form) {
const successMessage = document.createElement("div");
successMessage.classList.add("alert", "alert-success", "form-success");
const paragraph = document.createElement("p");
paragraph.classList.add("lead");
paragraph.appendChild(document.createTextNode("{{ "Form has been submitted successfully!"|t('freeform') }}"));
successMessage.appendChild(paragraph);
form.insertBefore(successMessage, form.childNodes[0]);
};
window.renderFormErrors = (errors, form) => {
const errorBlock = document.createElement("div");
errorBlock.classList.add("alert", "alert-danger", "errors");
const paragraph = document.createElement("p");
paragraph.appendChild(document.createTextNode("{{ "Error! Please review the form and try submitting again."|t('freeform') }}"));
paragraph.classList.add("lead");
errorBlock.appendChild(paragraph);
if (errors.length) {
const errorsList = document.createElement("ul");
for (let messageIndex = 0; messageIndex < errors.length; messageIndex++) {
const message = errors[messageIndex];
const listItem = document.createElement("li");
listItem.appendChild(document.createTextNode(message));
errorsList.appendChild(listItem);
}
errorBlock.appendChild(errorsList);
}
form.insertBefore(errorBlock, form.childNodes[0]);
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
If you do wish to use something less automated, check out the AJAX examples in the Freeform Demo Templates or check out the documentation below...
To submit a form using AJAX - pass the serialized form data as the payload when posting to any front-end URL.
Return values
The AJAX request must be a post
request and it will return a JSON object with the following values:
On successful single-page form post
success
- Aboolean
value oftrue
finished
- Aboolean
value oftrue
returnUrl
- The return URL specified for the formsubmissionId
- Anint
value of the submission ID if one was generatedhoneypot
- A JSobject
containing these values:name
- the generatedinput
name of the honeypot fieldhash
- the generated hash value that has to be submitted for the honeypot to validate
On form error
success
- Aboolean
value offalse
finished
- Aboolean
value offalse
formErrors
- Anarray
of translatable form error messageshoneypot
- A JSobject
containing these values:name
- the generatedinput
name of the honeypot fieldhash
- the generated hash value that has to be submitted for the honeypot to validate
errors
- Anobject
of field handles as keys and each containing an array of error messages.- An example, if the form's
firstName
andlastName
fields were required, but not filled out, the returning object would be:
- An example, if the form's
"errors": {
"firstName": ["This field is required"],
"lastName": ["This field is required"]
}
2
3
4
Usage in Templates
TIP
To see a variety of complete working AJAX examples for the most popular frameworks, install and check out our Demo Templates included with Freeform. The JS used in the demo templates work in the latest browsers with plain vanilla javascript and do not require any JS libraries, like jQuery, etc.
Here's an example of the JS code that turns any form into an AJAX-ready form. It strives to cover most use-cases and allows for easy customization:
<script>
var chrome = navigator.userAgent.indexOf("Chrome") > -1;
var explorer = navigator.userAgent.indexOf("MSIE") > -1;
var firefox = navigator.userAgent.indexOf("Firefox") > -1;
var safari = navigator.userAgent.indexOf("Safari") > -1;
var camino = navigator.userAgent.indexOf("Camino") > -1;
var opera = navigator.userAgent.toLowerCase().indexOf("op") > -1;
if (window.renderFormSuccess === undefined) {
function renderFormSuccess(form) {
var successMessage = document.createElement("div");
successMessage.classList.add("alert", "alert-success", "form-success");
var paragraph = document.createElement("p");
paragraph.classList.add("lead");
paragraph.appendChild(document.createTextNode("Form has been submitted successfully!"));
successMessage.appendChild(paragraph);
form.insertBefore(successMessage, form.childNodes[0]);
}
}
if (window.removeMessages === undefined) {
function removeMessages(form) {
// Remove any existing errors that are being shown
form.querySelectorAll("ul.errors").remove();
var fieldsWithErrors = form.querySelectorAll(".has-error");
for (var fieldIndex = 0; fieldIndex < fieldsWithErrors.length; fieldIndex++) {
var field = fieldsWithErrors[fieldIndex];
field.classList.remove("has-error");
}
// Remove success messages
form.querySelectorAll(".form-success").remove();
document.getElementsByClassName("form-errors").remove();
}
}
if (window.renderErrors === undefined) {
/**
* @param errors
* @param form
*/
function renderErrors(errors, form) {
for (var key in errors) {
if (!errors.hasOwnProperty(key) || !key) {
continue;
}
var messages = errors[key];
var errorsList = document.createElement("ul");
errorsList.classList.add("errors", "help-block");
for (var messageIndex = 0; messageIndex < messages.length; messageIndex++) {
var message = messages[messageIndex];
var listItem = document.createElement("li");
listItem.appendChild(document.createTextNode(message));
errorsList.appendChild(listItem);
}
var inputList = form.querySelectorAll("*[name=" + key + "], *[name='" + key + "[]']");
for (var inputIndex = 0; inputIndex < inputList.length; inputIndex++) {
var input = inputList[inputIndex];
input.parentElement.classList.add("has-error");
input.parentElement.appendChild(errorsList);
}
}
}
}
if (window.renderFormErrors === undefined) {
function renderFormErrors(errors, form) {
var errorBlock = document.createElement("div");
errorBlock.classList.add("alert", "alert-danger", "form-errors");
var paragraph = document.createElement("p");
paragraph.classList.add("lead");
paragraph.appendChild(document.createTextNode("Error! Please review the form and try submitting again."));
errorBlock.appendChild(paragraph);
if (errors.length) {
var errorsList = document.createElement("ul");
for (var messageIndex = 0; messageIndex < errors.length; messageIndex++) {
var message = errors[messageIndex];
var listItem = document.createElement("li");
listItem.appendChild(document.createTextNode(message));
errorsList.appendChild(listItem);
}
errorBlock.appendChild(errorsList);
}
form.insertBefore(errorBlock, form.childNodes[0]);
}
}
</script>
<script>
function lookForFormsToAjaxify() {
var forms = document.getElementsByTagName("form");
for (var formIndex = 0; formIndex < forms.length; formIndex++) {
var form = forms[formIndex];
if (!form.dataset.ajaxified) {
form.dataset.ajaxified = true;
form.addEventListener("submit", ajaxifyForm, false);
}
}
}
function ajaxifyForm(event) {
var form = event.target;
var data = new FormData(form);
var request = new XMLHttpRequest();
if (safari) {
for (var i = 0; i < form.elements.length; i++) {
if (form.elements[i].type == "file") {
if (form.elements[i].value == "") {
var elem = form.elements[i];
data.delete(elem.name);
}
}
}
}
var method = form.getAttribute("method");
var action = form.getAttribute("action");
request.open(method, action ? action : window.location.href, true);
request.setRequestHeader("Cache-Control", "no-cache");
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
request.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest");
request.onload = function () {
removeMessages(form);
if (request.status === 200) {
var response = JSON.parse(request.response);
if (response.success && response.finished) {
// Reset the form so that the user may enter fresh information
form.reset();
// ============================================================
// Uncomment this to have the form redirect to the success page
// ============================================================
// if (response.returnUrl) {
// window.location.href = response.returnUrl;
// }
renderFormSuccess(form);
} else if (response.errors || response.formErrors) {
renderErrors(response.errors, form);
renderFormErrors(response.formErrors, form);
}
if (response.honeypot) {
var honeypotInput = form.querySelector("input[name^=freeform_form_handle_]");
honeypotInput.setAttribute("name", response.honeypot.name);
honeypotInput.setAttribute("id", response.honeypot.name);
honeypotInput.value = response.honeypot.hash;
}
unlockSubmit(form);
} else {
console.error(request);
}
unlockSubmit(form);
};
request.send(data);
event.preventDefault();
}
function loadExternalForm(url, targetElement) {
var request = new XMLHttpRequest();
// Load the forms content into the #form-loader div
request.open("GET", url, true);
request.send();
request.onload = function () {
if (request.status === 200) {
targetElement.innerHTML = request.response;
// Activate all of the loaded scripts
var scripts = targetElement.querySelectorAll('script');
for (var index = 0; index < scripts.length; index++) {
var script = scripts[index];
var newScript = document.createElement('script');
newScript.innerHTML = script.innerHTML;
targetElement.appendChild(newScript);
script.parentNode.removeChild(script);
}
lookForFormsToAjaxify();
} else {
console.error(request);
}
};
}
/**
* Remove the "disabled" state of the submit button upon successful submit
*
* @property form
*/
function unlockSubmit(form) {
form.querySelector("button[type=submit]").removeAttribute("disabled");
if (typeof grecaptcha !== "undefined") {
grecaptcha.reset();
}
}
// Add remove prototypes
Element.prototype.remove = function () {
this.parentElement.removeChild(this);
};
NodeList.prototype.remove = HTMLCollection.prototype.remove = function () {
for (var i = this.length - 1; i >= 0; i--) {
if (this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
};
lookForFormsToAjaxify();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
Caching and Refreshing CSRF Token
If you're caching a form and need to manually refresh the CSRF token, you may need something like this:
<div id="target"></div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$(() => {
$.ajax({
url: "/ajax",
success: (response) => {
$("#target").html(response);
$('input[name={{ craft.app.config.general.csrfTokenName }}]').val('{{ craft.app.request.csrfToken }}');
}
});
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
reCAPTCHA and Loading Entire Form via AJAX
If you're loading an entire form via AJAX and using reCAPTCHA, you'll need to load the reCAPTCHA JS yourself, since it's considered insecure otherwise and the browser blocks it. You should add this script tag anywhere on your page, preferably the footer:
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?render=explicit"></script>
Redirect AJAX-enabled Forms
If you want to redirect an AJAX-enabled form after a successful or unsuccessful submit, you would need to attach event listeners to the form HTML element:
// A successful form submit will emit a "form.submit.success" event
form.addEventListener("form.submit.success", (event) => {
var formElement = event.form;
var response = event.response;
// Redirect the page using the response's return URL
window.location.href = response.returnUrl;
});
// An unsuccessful form submit will emit a "form.submit.error" event
form.addEventListener("form.submit.error", (event) => {
var formElement = event.form;
var response = event.response;
// Do whatever checks on errors you need to do, if any
var errors = response.errors;
// Redirect the page using the response's return URL
window.location.href = response.returnUrl;
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20